core: fts5-тест проверяет регистронезависимость кириллицы
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Мутационная проверка: ломаем боевой код и смотрим, падает ли нужный тест.
|
||||
|
||||
Формат e2e/mutations.tsv: file<TAB>old<TAB>new<TAB>must_fail_test
|
||||
Прогон: python3 e2e/mutation_check.py --gradle ./gradlew
|
||||
Выход 0 — все мутации убиты (тесты ловят поломку). 1 — есть выжившие (дыра в покрытии).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def run_tests(gradle: str) -> dict[str, str]:
|
||||
"""Возвращает {test_name: status}, где status = PASS/FAIL."""
|
||||
subprocess.run([gradle, "--quiet", "test", "--rerun-tasks"],
|
||||
capture_output=True, text=True, timeout=1800)
|
||||
status: dict[str, str] = {}
|
||||
for f in glob.glob("**/build/test-results/test/*.xml", recursive=True):
|
||||
try:
|
||||
root = ET.parse(f).getroot()
|
||||
except ET.ParseError:
|
||||
continue
|
||||
for tc in root.iter("testcase"):
|
||||
failed = any(c.tag in ("failure", "error") for c in tc)
|
||||
status[tc.get("name")] = "FAIL" if failed else "PASS"
|
||||
return status
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--gradle", default="./gradlew")
|
||||
ap.add_argument("--mutations", default="e2e/mutations.tsv")
|
||||
a = ap.parse_args()
|
||||
|
||||
rows = []
|
||||
for line in open(a.mutations, encoding="utf-8"):
|
||||
line = line.rstrip("\n")
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
f, old, new, must_fail = line.split("\t")
|
||||
rows.append((f, old.replace("\\n", "\n"), new.replace("\\n", "\n"), must_fail))
|
||||
|
||||
print("=== базовая линия (без мутаций) ===")
|
||||
base = run_tests(a.gradle)
|
||||
print(f"тестов найдено: {len(base)}; упавших: {[k for k, v in base.items() if v == 'FAIL']}")
|
||||
|
||||
survivors = []
|
||||
for f, old, new, must_fail in rows:
|
||||
backup = f + ".bak"
|
||||
shutil.copy2(f, backup)
|
||||
try:
|
||||
src = open(f, encoding="utf-8").read()
|
||||
assert old in src, f"МУТАЦИЯ НЕ ПРИМЕНИЛАСЬ (нет фрагмента) в {f}: {old[:60]!r}"
|
||||
open(f, "w", encoding="utf-8").write(src.replace(old, new, 1))
|
||||
st = run_tests(a.gradle)
|
||||
got = st.get(must_fail, "НЕ НАЙДЕН")
|
||||
killed = got == "FAIL"
|
||||
print(f"{'УБИТА ' if killed else 'ВЫЖИЛА'} | {must_fail} -> {got} | {f}: {old[:50]} -> {new[:30]}")
|
||||
if not killed:
|
||||
survivors.append((must_fail, f, old, new))
|
||||
finally:
|
||||
shutil.move(backup, f)
|
||||
|
||||
print("\n=== восстановление ===")
|
||||
st = run_tests(a.gradle)
|
||||
print(f"после отката упавших: {[k for k, v in st.items() if v == 'FAIL']}")
|
||||
|
||||
if survivors:
|
||||
print("\nВЫЖИВШИЕ МУТАЦИИ (дыры в покрытии):")
|
||||
for t, f, old, new in survivors:
|
||||
print(f" - {t}: {f} `{old[:60]}` -> `{new[:40]}`")
|
||||
return 1
|
||||
print("\nВСЕ МУТАЦИИ УБИТЫ")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user