-
Notifications
You must be signed in to change notification settings - Fork 1
115 lines (107 loc) · 3.84 KB
/
Copy pathmutation.yml
File metadata and controls
115 lines (107 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
name: Mutation
# Mutation testing (StrykerJS) — issue #1028.
#
# WARNING, not a PR gate (issue #1056): this workflow runs weekly + on demand
# only — never on pull_request — so it can NEVER block a PR or merge. The
# per-package Stryker configs set a `thresholds.break` floor (libs/*/stryker.conf.mjs,
# ~6 pts under the CI baseline); a real mutation-score regression makes this
# weekly run go red (a visible signal in the Actions tab), instead of vanishing
# into a green check.
#
# Mutation runs are slow (a full pass mutates every source file and re-runs
# the suite per mutant), which is why this is off the per-PR hot path.
permissions:
contents: read
on:
workflow_dispatch:
schedule:
# Weekly, Monday 06:00 UTC.
- cron: "0 6 * * 1"
jobs:
mutation:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package:
[
act,
act-pg,
act-sqlite,
act-tck,
act-http,
act-crypto,
act-patch,
act-ops,
]
services:
# Always provisioned; only @rotorsoft/act-pg's specs connect to it
# (port 5431, mirroring the unit-test CI job in ci-cd.yml).
postgres:
image: postgres:18-alpine
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5431:5432
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
run_install: false
- uses: actions/setup-node@v7
with:
node-version: 22.23.2
cache: "pnpm"
- run: pnpm install
- name: Run mutation testing (${{ matrix.package }})
run: pnpm --filter @rotorsoft/${{ matrix.package }} run mutation
# Compute the mutation score from Stryker's JSON report and append it
# to the run summary. Score = (Killed + Timeout) / (Killed + Timeout +
# Survived + NoCoverage) — the standard StrykerJS mutation score.
- name: Publish mutation score (${{ matrix.package }})
if: always()
run: |
node -e '
const fs = require("fs");
const pkg = "${{ matrix.package }}";
const f = `libs/${pkg}/reports/mutation/mutation.json`;
const out = process.env.GITHUB_STEP_SUMMARY;
if (!fs.existsSync(f)) {
fs.appendFileSync(out, `## Mutation — ${pkg}\n\nNo report produced.\n\n`);
process.exit(0);
}
const report = JSON.parse(fs.readFileSync(f, "utf8"));
const counts = {};
for (const file of Object.values(report.files ?? {})) {
for (const m of file.mutants ?? []) {
counts[m.status] = (counts[m.status] ?? 0) + 1;
}
}
const detected = (counts.Killed ?? 0) + (counts.Timeout ?? 0);
const undetected = (counts.Survived ?? 0) + (counts.NoCoverage ?? 0);
const valid = detected + undetected;
const score = valid ? ((detected / valid) * 100).toFixed(2) : "n/a";
const md = [
`## Mutation — ${pkg}`,
"",
`**Mutation score: ${score}%** (non-blocking baseline)`,
"",
"| Status | Count |",
"|---|---:|",
...Object.entries(counts).map(([k, v]) => `| ${k} | ${v} |`),
"",
].join("\n");
fs.appendFileSync(out, md + "\n");
'
- name: Upload mutation report (${{ matrix.package }})
if: always()
uses: actions/upload-artifact@v7
with:
name: mutation-report-${{ matrix.package }}
path: libs/${{ matrix.package }}/reports/mutation/
if-no-files-found: ignore