96 lines
4.9 KiB
Python
Executable file
96 lines
4.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
"""P8-REVIEW: mutation harness. Plants one named mutation into the WORKING COPY, leaving the
|
||
repository untouched, and prints the file it changed. Reverting is a copy back from the repo.
|
||
|
||
Usage: plant.py <id> plant|revert
|
||
"""
|
||
import shutil, subprocess, sys, os
|
||
|
||
# Оба переопределяются: копия — там, где её положила сессия, репозиторий — источник, из которого
|
||
# делается откат. Дефолты совпадают с рецептом в README этого каталога.
|
||
REPO = os.environ.get("P8_REPO", os.path.expanduser("~/projects/textmachine/platform"))
|
||
COPY = os.environ.get("P8_COPY", os.path.expanduser("~/tm-p8-review/mine/platform"))
|
||
|
||
M = {
|
||
# id: (file, old, new, packages that must be re-run)
|
||
|
||
# --- дописано после ревью старшей моделью: улики M1/M2/M9 не были воспроизводимы из артефактов,
|
||
# --- плюс шесть пере-проверок «выживших» на ПОЛНОЙ батарее вместо пакетного сабсета.
|
||
"M1": ("internal/pgstore/credits.go",
|
||
'\t\tif unit != nil {\n\t\t\treturn fmt.Errorf("%w: %s", ErrAttemptSpawned, *unit)\n\t\t}',
|
||
'\t\t_ = unit // MUTATION M1: re-check removed',
|
||
"ALL"),
|
||
"M2": ("internal/runs/reconcile.go",
|
||
'\tif bound != nil && *bound < committed {\n\t\tcommitted = *bound\n\t}',
|
||
'\t_ = bound // MUTATION M2: SpendBound clamp removed',
|
||
"ALL"),
|
||
"M9": ("internal/metrics/metrics.go",
|
||
'\tif unfinished {\n\t\tm.sweepUnfinished.WithLabelValues(name).Inc()\n\t}\n',
|
||
'\t// MUTATION M9: an unfinished pass is not counted\n',
|
||
"ALL"),
|
||
"M11": ("internal/pgstore/runs.go",
|
||
'\t\tselect min(a.spend_baseline_micro_usd)',
|
||
'\t\tselect max(a.spend_baseline_micro_usd) -- MUTATION M11',
|
||
"ALL"),
|
||
"M12": ("internal/pgstore/sessions.go",
|
||
'now.Add(idleTTL), now.Add(maxAge))',
|
||
'now.Add(idleTTL), now.Add(100*maxAge))',
|
||
"ALL"),
|
||
"M13": ("internal/auth/cookie.go",
|
||
'func (c Cookies) ClearSession(w http.ResponseWriter) { c.set(w, c.SessionName(), "", -time.Second) }',
|
||
'func (c Cookies) ClearSession(w http.ResponseWriter) { c.set(w, c.SessionName(), "", time.Hour) } // MUTATION M13',
|
||
"ALL"),
|
||
"M14": ("internal/auth/cookie.go",
|
||
'\tif ttl < 0 {\n\t\tmaxAge = -1\n\t}',
|
||
'\tif ttl < 0 {\n\t\tmaxAge = 3600 // MUTATION M14\n\t}',
|
||
"ALL"),
|
||
"M15": ("cmd/tmplatformd/main.go",
|
||
'const loginJournalRetention = 180 * 24 * time.Hour',
|
||
'const loginJournalRetention = 180 * 365 * 24 * time.Hour // MUTATION M15',
|
||
"ALL"),
|
||
"M16": ("internal/pgstore/identity.go",
|
||
'`delete from login_events where at < $1`',
|
||
'`delete from login_events where at < $1 and 1=0`',
|
||
"ALL"),
|
||
"M3": ("internal/pgstore/credits.go",
|
||
'\tif !applied {\n\t\t// The ledger already holds this attempt id, so nothing was debited. Reporting success\n\t\t// would spawn a run against credit that was never reserved.\n\t\treturn ErrDuplicateHold\n\t}',
|
||
'\tif !applied {\n\t\treturn nil // MUTATION M3\n\t}',
|
||
"./internal/pgstore/ ./internal/runs/"),
|
||
"M4": ("internal/pgstore/credits.go",
|
||
'\tif spent < 0 {\n\t\treturn fmt.Errorf("pgstore: spend cannot be negative, got %d", spent)\n\t}\n',
|
||
'\t// MUTATION M4: negative-spend guard removed\n',
|
||
"./internal/pgstore/ ./internal/runs/"),
|
||
"M5": ("internal/runs/reconcile.go",
|
||
'\tif committed < *l.SpendBaseline {\n\t\treturn 0, nil\n\t}\n',
|
||
'\t// MUTATION M5: clamp-at-zero removed\n',
|
||
"./internal/runs/ ./internal/pgstore/"),
|
||
"M6": ("internal/auth/cookie.go",
|
||
'\t\tSecure: !c.Insecure,',
|
||
'\t\tSecure: false, // MUTATION M6',
|
||
"./internal/auth/ ./internal/login/ ./internal/httpapi/ ./cmd/tmplatformd/"),
|
||
"M7": ("internal/runs/reconcile.go",
|
||
'\treturn context.WithTimeout(ctx, time.Until(deadline)/2)',
|
||
'\treturn context.WithTimeout(ctx, time.Until(deadline)) // MUTATION M7',
|
||
"./internal/runs/ ./cmd/tmplatformd/"),
|
||
"M8": ("internal/metrics/metrics.go",
|
||
'\tm.stalledRuns.Set(float64(r.StalledRuns))\n',
|
||
'\t// MUTATION M8: the stalled gauge is never set\n',
|
||
"./internal/metrics/ ./cmd/tmplatformd/ ./internal/runs/"),
|
||
"M10": ("internal/metrics/metrics.go",
|
||
'\tm.abandonedSurfaces.Set(float64(r.AbandonedSurfaces))\n',
|
||
'\t// MUTATION M10: the abandoned-surfaces gauge is never set\n',
|
||
"ALL"),
|
||
}
|
||
|
||
mid, action = sys.argv[1], sys.argv[2]
|
||
f, old, new, pkgs = M[mid]
|
||
src, dst = os.path.join(REPO, f), os.path.join(COPY, f)
|
||
shutil.copyfile(src, dst)
|
||
if action == "revert":
|
||
print(f"{mid}: reverted {f}")
|
||
sys.exit(0)
|
||
s = open(dst).read()
|
||
if old not in s:
|
||
sys.exit(f"{mid}: ANCHOR NOT FOUND in {f}")
|
||
open(dst, "w").write(s.replace(old, new, 1))
|
||
print(f"{mid}: planted in {f}; packages to run: {pkgs}")
|