simbiot-harness/tests/test_code_gate.py
Simbiot Harness 9badb02951 гейт кода в закрытии задачи: правка харнеса = коммит + тест + зелёный тест
Кейс 2026-10-18 (t-261018103940): Коди закрыл задачу итогом «x» до конца
правки tg_inbox, без теста и без коммита. Проверка — в complete (не в промпте):
- незакоммиченные правки harness/ cli.py tests/ scripts/, сделанные тиком
  задачи после её клейма → отказ со списком файлов. «Сделанные тиком» — по
  трассе: edit/write по пути, shell — путь + признак записи (sed -i, >, tee,
  cp/mv/rm, patch, git apply, open(…,'w')); чтение (grep, sed -n) и запуск
  (python cli.py …) — не правка; нет трассы — по времени изменения файла.
  Иначе чужая незакоммиченная правка того же времени блокировала бы задачу;
- коммиты после клейма (точно по %ct, не по секунде --since) меняют код без
  tests/test_*.py → отказ; исключение — complete --no-test «причина»
  (причина уходит Alex в итоге);
- изменённые тесты запускаются при закрытии, FAIL/падение → отказ;
- в итог Alex автоматически: «Код: <хеши> · тесты PASS: …»;
- правило — в постановке батча задач; SIMBIOT_CODE_GATE=off выключает.
Сухой прогон на задаче Коди: сейчас — «Код: 70a2015 · тесты PASS:
tests/test_tg_group_ux.py»; в 10:42 был бы отказ (tg_inbox.py не закоммичен).
Тест: test_code_gate 14 PASS; полный набор — без регрессий.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:06:44 +00:00

155 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""Гейт кода в закрытии задачи (commands.code_change_check) — hermetic, временный git-репо.
Запуск: ./venv/bin/python tests/test_code_gate.py
Кейс 2026-10-18: Коди закрыл задачу итогом «x» до конца правки tg_inbox, без
теста и коммита. Проверяет: задача без правок кода — закрывается как раньше;
незакоммиченная правка после клейма — отказ; старая чужая грязь (до клейма) —
не мешает; код без теста — отказ, с --no-test «причина» — можно, причина в
итоге; код + зелёный тест — закрыта, хеш и тест в итоге; красный тест — отказ;
SIMBIOT_CODE_GATE=off — гейт выключен.
"""
import os
import subprocess
import sys
import tempfile
import time
sys.path.insert(0, "/root/simbiot-harness")
_tmp = tempfile.mkdtemp(prefix="simbiot_codegate_")
os.environ["SIMBIOT_DB"] = os.path.join(_tmp, "harness.db")
REPO = os.path.join(_tmp, "repo")
os.environ["SIMBIOT_CODE_REPO"] = REPO
os.environ.pop("SIMBIOT_CODE_GATE", None)
from harness import commands, es
es.init_db()
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
if not cond:
fails.append(name)
def git(*a):
subprocess.run(["git", "-C", REPO, "-c", "user.name=t", "-c", "user.email=t@t", *a],
check=True, capture_output=True)
def write(rel, text, mtime=None):
p = os.path.join(REPO, rel)
os.makedirs(os.path.dirname(p), exist_ok=True)
open(p, "w").write(text)
if mtime:
os.utime(p, (mtime, mtime))
os.makedirs(REPO)
git("init", "-q")
write("harness/a.py", "x = 1\n")
write("scripts/old.sh", "echo old\n")
git("add", ".")
git("commit", "-q", "-m", "init")
write("scripts/old.sh", "echo old dirty\n", mtime=time.time() - 3600) # чужая старая грязь
N = [0]
def task():
N[0] += 1
tid = f"t-{N[0]}"
es.append_event("task", tid, "TaskSubmitted", {"id": tid, "title": "правка", "size": "S"})
es.append_event("task", tid, "TaskClaimed", {"level": "min"})
time.sleep(1.1) # git --since с точностью до секунды
return tid
def done(tid, answer="сделана правка харнеса", **kw):
try:
commands.complete_task_with_delivery(tid, answer, level="min", **kw)
return True, ""
except ValueError as e:
return False, str(e)
def answer_of(tid):
import json
return [json.loads(e["data"])["answer"] for e in es.get_events(0, 10**6)
if e["event_type"] == "TaskCompleted" and e["aggregate_id"] == tid][-1]
t = task()
ok, why = done(t, "ответил в чат, код не трогал")
check("без правок кода — закрывается как раньше, итог без приписки",
ok and "Код:" not in answer_of(t), why)
check("старая чужая грязь (до клейма) не мешает", ok)
t = task()
write("harness/a.py", "x = 2\n")
ok, why = done(t)
check("незакоммиченная правка — отказ с файлом", not ok and "harness/a.py" in why, why)
git("add", "harness/a.py")
git("commit", "-q", "-m", "a=2")
ok, why = done(t)
check("код без теста — отказ", not ok and "без теста" in why, why)
ok, why = done(t, no_test_reason="правка константы, тест невозможен")
check("--no-test с причиной — закрыта, причина в итоге",
ok and "без теста: правка константы" in answer_of(t), why)
t = task()
write("harness/a.py", "x = 3\n")
write("tests/test_a.py", "print('PASS a ok')\n")
git("add", ".")
git("commit", "-q", "-m", "a=3 + test")
ok, why = done(t)
ans = answer_of(t) if ok else ""
check("код + зелёный тест — закрыта", ok, why)
check("в итоге хеш коммита и тест", "Код: " in ans and "тесты PASS: tests/test_a.py" in ans, ans)
t = task()
write("harness/a.py", "x = 4\n")
write("tests/test_a.py", "print('FAIL a broken')\nraise SystemExit(1)\n")
git("add", ".")
git("commit", "-q", "-m", "a=4 broken test")
ok, why = done(t)
check("красный тест — отказ", not ok and "не проходит" in why, why)
# чужая незакоммиченная правка того же времени: тик её не трогал — не блокирует
N[0] += 1
tid = f"t-{N[0]}"
es.append_event("task", tid, "TaskSubmitted", {"id": tid, "title": "правка", "size": "S"})
es.append_event("task", tid, "TaskClaimed", {"level": "min", "session_id": "s-min-777"})
time.sleep(1.1)
es.append_event("trace", "min-777", "ToolCalled", {"trace_id": "min-777", "tool": "edit",
"input": '{"path": "/x/repo/harness/b.py"}'})
es.append_event("trace", "min-777", "ToolCalled", {"trace_id": "min-777", "tool": "shell",
"input": '{"command": "./venv/bin/python cli.py tg-reply m-1 \\"ok\\""}'})
es.append_event("trace", "min-777", "ToolCalled", {"trace_id": "min-777", "tool": "shell",
"input": '{"command": "grep -n tg_reply cli.py 2>/dev/null | head"}'})
es.append_event("trace", "min-777", "ToolCalled", {"trace_id": "min-777", "tool": "shell",
"input": '{"command": "sed -i s/a/b/ scripts/mine.sh"}'})
write("cli.py", "# чужая правка cli\n") # запускали и читали, но не правили — не своё
write("scripts/mine.sh", "echo b\n") # правил sed -i — своё
write("harness/b.py", "y = 1\n") # своя правка (в трассе)
write("scripts/other.sh", "echo чужое\n") # чужая правка (не в трассе)
ok, why = done(tid)
check("своя незакоммиченная правка (в трассе) — отказ", not ok and "harness/b.py" in why, why)
check("чужая правка того же времени не в списке", "scripts/other.sh" not in why, why)
check("запуск и grep cli.py — не правка cli.py", "cli.py" not in why, why)
check("sed -i в shell — своя правка", "scripts/mine.sh" in why, why)
git("add", "harness/b.py", "scripts/mine.sh")
write("tests/test_a.py", "print('PASS b ok')\n")
git("add", "tests/test_a.py")
git("commit", "-q", "-m", "b + test")
ok, why = done(tid)
check("свою закоммитил — закрыта, чужая грязь не мешает", ok, why)
os.environ["SIMBIOT_CODE_GATE"] = "off"
ok, why = done(t)
check("SIMBIOT_CODE_GATE=off — гейт выключен", ok, why)
print(f"\n{'OK' if not fails else 'FAILED'}: {len(fails)} fail(s)")
sys.exit(1 if fails else 0)