Files
Agent-System-Dev/tests/test_svc_realcombo.py
T
weichen 2c767867ca
ci / test-pg (pull_request) Failing after 1m19s
ci / svc-realcombo (pull_request) Failing after 1m27s
ci / test (pull_request) Failing after 2m23s
Hard-disable RDM exec tools in realcombo tests
Why: CI runners lack bwrap, so the real RDM binary refuses to start
under the sandbox fail-closed policy (run 2607: serve exits right after
launch); the product provides RDM_DISABLE_EXEC_TOOLS as the top-level
fallback and lifecycle semantics under test are unchanged.
Evidence: local rerun with the env set stays green.
Impact: tests only.
2026-10-12 14:36:32 +00:00

208 lines
9.9 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.
"""svc.sh 真实组合门禁(U3,2026-10-12 统一部署方案)。
用**真实产物**(agent-system 组合二进制 + agent-system-rdm 组合二进制)而非 bash
桩验证部署生命周期:identity 领养/ensure/verify(真 SQLite schema_migrations +
agent_system_identity)、裸重启身份稳定、能力探测契约(capabilities v1)、
RDM 全轨部署(物化→门禁→release 切换→伴生工作台)、真实 PostgreSQL 身份
(AGENT_SYSTEM_DSN 环境交付,凭据不进命令行)。
运行条件(缺一即显式 NOT_RUN,不静默跳过):
SVC_TEST_REAL=1 —— 总开关
SVC_REAL_BIN=<agent-system> —— 真实基座/组合二进制(make build 产物)
SVC_REAL_RDM_BIN=<agent-system-rdm> —— 真实 RDM 组合二进制(make build 产物)
SVC_RDM_ORIGIN=<RDM 仓远端> —— (可选)全轨部署用;缺省仅做制品级生命周期
SVC_TEST_PG=1 + AGENT_PG_TEST_DSN —— (可选)真实 PG 身份专项
桩套件(test_svc*.py)不覆盖真实产物行为——本文件是「声明≠能力」的最后一道门。
"""
import importlib.util
import json
import os
import shutil
import sqlite3
import subprocess
import tempfile
import unittest
import urllib.request
from pathlib import Path
SOURCE = Path(__file__).resolve().parents[1]
REAL = os.environ.get("SVC_TEST_REAL") == "1"
REAL_BIN = os.environ.get("SVC_REAL_BIN", "")
REAL_RDM_BIN = os.environ.get("SVC_REAL_RDM_BIN", "")
RDM_ORIGIN = os.environ.get("SVC_RDM_ORIGIN", "")
PG = os.environ.get("SVC_TEST_PG") == "1" and bool(os.environ.get("AGENT_PG_TEST_DSN"))
_spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py")
tsr = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(tsr)
@unittest.skipUnless(REAL, "真实组合专项 NOT_RUN:需 SVC_TEST_REAL=1(详见文件头运行条件)")
class RealComboTests(tsr.SvcAutoRecoverTests):
def setUp(self):
super().setUp()
shutil.copy2(SOURCE / "scripts" / "svc_manifest.py",
self.root / "scripts" / "svc_manifest.py")
self.real_bin = Path(REAL_BIN).resolve() if REAL_BIN else None
if self.real_bin is not None:
self.assertTrue(self.real_bin.is_file() and os.access(self.real_bin, os.X_OK),
"SVC_REAL_BIN 不可执行:%s" % self.real_bin)
def manifest(self, name):
p = self.runtime(name) / "manifest.json"
return json.loads(p.read_text(encoding="utf-8")) if p.exists() else None
def identity_row(self, name):
db = self.runtime(name) / "data" / "agent-system.db"
con = sqlite3.connect(str(db))
try:
return list(con.execute(
"SELECT instance_id, dbref FROM agent_system_identity"))
finally:
con.close()
def http_ok(self, port):
try:
with urllib.request.urlopen("http://127.0.0.1:%d/" % port, timeout=3) as r:
return r.status == 200
except OSError:
return False
def seed_real(self, name, port, binary):
# CI 运行器无 bwrap:RDM 沙箱 fail-closed 会拒启——硬关 exec 工具
#(产品提供的最高兜底,RDM_DISABLE_EXEC_TOOLS=1)后生命周期语义不变。
self.env["RDM_DISABLE_EXEC_TOOLS"] = "1"
d = self.runtime(name)
(d / "bin").mkdir(parents=True, exist_ok=True)
(d / "data").mkdir(exist_ok=True)
(d / "plugins").mkdir(exist_ok=True)
shutil.copy2(binary, d / "bin" / "agent-system")
(d / "svc.env").write_text("".join("%s=%s\n" % kv for kv in [
("kind", "agent"), ("env", name), ("pid", ""),
("addr", "127.0.0.1:%d" % port), ("tags", ""),
("source", "realcombo"), ("version", "real"), ("started", ""),
("pidcheck", str(d / "bin" / "agent-system")),
("exec", "--addr 127.0.0.1:%d --data %s --plugins %s --auth auto"
% (port, d / "data", d / "plugins")),
]))
@unittest.skipUnless(REAL and REAL_BIN,
"真实基座二进制专项 NOT_RUN:需 SVC_REAL_BIN")
def test_U3_real_agent_identity_lifecycle_sqlite(self):
"""真实组合二进制:领养 ensure → verify → 两次裸重启身份/锚点稳定;
capabilities v1 探测契约。"""
self.seed_real("rc1", 9471, self.real_bin)
result = self.svc("rc1", "start")
self.assertIn("已启动", result.stdout)
m = self.manifest("rc1")
self.assertIsNotNone(m, "start 应完成 manifest 领养")
iid = m["instance_id"]
self.assertEqual(self.identity_row("rc1"), [(iid, "sqlite")],
"领养须在真实库建身份行")
# schema 是真实基座写入的(非桩)
con = sqlite3.connect(str(self.runtime("rc1") / "data" / "agent-system.db"))
self.assertTrue(con.execute(
"SELECT count(*) FROM schema_migrations").fetchone()[0] >= 1)
con.close()
self.assertTrue(self.http_ok(9471))
# capabilities v1 契约(探测无副作用形态)
cap = subprocess.run([str(self.runtime("rc1") / "bin" / "agent-system"),
"identity", "capabilities"],
capture_output=True, text=True, timeout=15)
self.assertEqual(cap.returncode, 0, cap.stderr)
self.assertIn("identity-capabilities: v1", cap.stdout)
# 错 id verify 拒绝
bad = subprocess.run([str(self.runtime("rc1") / "bin" / "agent-system"),
"identity", "verify", "--instance-id",
"svc-rc1-zzzzzz999999",
"--data", str(self.runtime("rc1") / "data")],
capture_output=True, text=True, timeout=15)
self.assertEqual(bad.returncode, 1)
self.svc("rc1", "restart")
self.svc("rc1", "restart")
self.assertEqual(self.identity_row("rc1"), [(iid, "sqlite")],
"裸重启不得换身份")
self.assertEqual(self.manifest("rc1")["instance_id"], iid)
self.assertTrue(self.http_ok(9471))
self.svc("rc1", "stop")
@unittest.skipUnless(REAL_RDM_BIN, "真实 RDM 专项 NOT_RUN:需 SVC_REAL_RDM_BIN")
def test_U3_real_rdm_binary_lifecycle_sqlite(self):
"""真实 RDM 组合二进制:identity 接线(Backend 公共 CLI)→ serve 门禁 →
领域层可初始化。"""
rdm_bin = Path(REAL_RDM_BIN).resolve()
self.seed_real("rcr", 9473, rdm_bin)
result = self.svc("rcr", "start")
self.assertIn("已启动", result.stdout)
m = self.manifest("rcr")
iid = m["instance_id"]
self.assertEqual(self.identity_row("rcr"), [(iid, "sqlite")])
self.assertTrue(self.http_ok(9473))
# RDM 未知子命令明确退出(不落服务路径)
bogus = subprocess.run([str(self.runtime("rcr") / "bin" / "agent-system"),
"totally-bogus"],
capture_output=True, text=True, timeout=10)
self.assertEqual(bogus.returncode, 2, bogus.stderr)
self.svc("rcr", "stop")
@unittest.skipUnless(RDM_ORIGIN, "全轨部署 NOT_RUN:需 SVC_RDM_ORIGIN(RDM 源仓远端)")
def test_U3_real_rdm_full_deploy_track(self):
"""全轨:materialize(真实 make build)→ 候选身份门禁 → release 切换 →
伴生工作台 200 → 裸重启稳定。"""
env = dict(self.env)
env["RDM_DISABLE_EXEC_TOOLS"] = "1"
env.update({"SVC_RDM_SRC_DIR": str(self.root / "rdmcache"),
"SVC_RDM_ORIGIN": RDM_ORIGIN})
result = self.svc("rcf", "deploy", "--from", "rdm", "--port", "9474", env=env)
self.assertIn("已切换 release", result.stdout)
d = self.runtime("rcf")
self.assertTrue((d / "current" / "bin" / "agent-system").exists())
iid = self.manifest("rcf")["instance_id"]
self.assertEqual(self.identity_row("rcf"), [(iid, "sqlite")])
# 伴生工作台(真实 web/)
addr = self.kv("rcfweb", "addr")
self.assertIsNotNone(addr, "伴生 rcfweb 未登记")
port = int(addr.rsplit(":", 1)[1])
self.assertTrue(self.http_ok(port))
self.assertIn("RDM", (d / "current" / "htdocs" / "index.html").read_text()
if (d / "current" / "htdocs" / "index.html").exists()
else "rdm", "工作台内容")
self.svc("rcf", "restart")
self.assertEqual(self.identity_row("rcf"), [(iid, "sqlite")])
self.assertTrue(self.http_ok(9474))
self.svc("rcf", "stop")
self.svc("rcfweb", "stop")
@unittest.skipUnless(PG and REAL_RDM_BIN,
"真实 PG 专项 NOT_RUN:需 SVC_TEST_PG=1 + AGENT_PG_TEST_DSN + SVC_REAL_RDM_BIN")
def test_U3_real_pg_identity_env_delivery(self):
"""真实 PostgreSQL:AGENT_SYSTEM_DSN 环境交付 ensure/verify(凭据不进
命令行);错 id fail-closed。"""
rdm_bin = Path(REAL_RDM_BIN).resolve()
data = self.root / "pgdata"
data.mkdir()
env = dict(os.environ)
env["AGENT_SYSTEM_DSN"] = os.environ["AGENT_PG_TEST_DSN"]
env.pop("SVC_STUB_TRACE", None)
iid = "svc-realcombo-pg-%d" % os.getpid()
run = lambda *a: subprocess.run(
[str(rdm_bin), "identity", *a, "--instance-id", iid,
"--dbref", "pg", "--data", str(data)],
env=env, capture_output=True, text=True, timeout=60)
r = run("ensure")
self.assertEqual(r.returncode, 0, r.stderr)
r = run("verify")
self.assertEqual(r.returncode, 0, r.stderr)
bad = subprocess.run(
[str(rdm_bin), "identity", "verify", "--instance-id",
"svc-realcombo-pg-wrong", "--dbref", "pg", "--data", str(data)],
env=env, capture_output=True, text=True, timeout=60)
self.assertEqual(bad.returncode, 1)
if __name__ == "__main__":
unittest.main()