Files
codex-app-server/cloud/tests/test_chv_arch95.py
T
host c908a0147a feat(chv): 架构与代码质量提升——WarmPool 定案、长函数提取、严格测试替身、Docker 同步守卫、配置统一校验、类型注解与 ADR
堆叠于 #246(feat-chv-secfix),只到 PR OPEN,未合并、未部署。
安全语义不变:egress、key seed 投递、磁盘删除、默认 provisioned、
tap 校验一律保留;前端零改动。
- WarmPool 定案为实验性(ADR-5):dispatch 永远冷启动,
  wired_to_dispatch 恒 False,acquire/resume 只保留实验接口,
  worker 仍调 reconcile_orphans 清理残留。
- 长函数提取:preflight 126→31、wake_vm 135→36、__init__ 69→31、
  build_chv_config 159→58;错误文案逐字不变。
- 严格测试替身:fake API 校验 VmConfig(任务盘+seed 盘、net
  无重复),fake SSH 校验 known_hosts 钉死;新增 5 项测试。
- Docker 同步守卫:8 个真实标记用例下 Docker 内联版与正典
  parse_goal_result 状态全等,改分叉即 fail。
- 配置统一校验:preflight 以 config_invalid 统一兜底端口/配额/
  超时/快照/host-key 模式(构造时已校验正常值,preflight 兜底
  事后改写);CHV_NET 与 CHV_NET_JSON 双设仍警告不硬拒。
- 类型与 lint:chv 叶子模块公开 API 补类型注解;stdlib 扫描
  揪出 executor 内 1 个真死 import(host_key_paths)并删除。
- ADR:docs/chv-architecture.md(ADR-1 至 ADR-7)。
测试:CHV 163 项全过;cloud 全量 1593 项三跑结果为 8 errors
(首跑详细日志因 tail 截断丢失)、全过、1 失败(浏览器代理
时序测试,与 CHV 无关),全量本身存在时序偶发,CHV 套件
确定性全过。
2026-10-31 05:50:56 +00:00

259 lines
10 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.
"""CHV 架构 9.5 提升轨测试(feat-chv-arch95,堆叠于 #246)。
只用标准库与 mock,绝不启动真实 VM。本文件是“严格测试替身”
的载体:替身不再无条件放行——fake API 校验 VmConfig 内容
(任务盘 + seed 盘、net 条目),fake SSH 校验 known_hosts
钉死与 StrictHostKeyChecking=yes,mock 再也不能把被测逻辑
本身也 mock 掉。测试里只用 FAKE 占位密钥,绝无真实密钥。
"""
import os
import sys
import tempfile
import unittest
from pathlib import Path
try:
from cloud.chv.pool import WarmPool
from cloud.sandbox import (
ChvFailure,
CloudHypervisorExecutor,
DockerExecutor,
parse_goal_result,
)
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.chv.pool import WarmPool
from cloud.sandbox import (
ChvFailure,
CloudHypervisorExecutor,
DockerExecutor,
parse_goal_result,
)
class FakeResult:
def __init__(self, returncode=0, stdout="", stderr=""):
self.returncode = returncode
self.stdout = stdout
self.stderr = stderr
class FakeProc:
def __init__(self, alive=True):
self._alive = alive
def poll(self):
return None if self._alive else 1
def terminate(self):
self._alive = False
def wait(self, timeout=None):
return 0
def kill(self):
self._alive = False
def _write(path, content=b"x"):
with open(path, "wb") as fh:
fh.write(content)
os.chmod(path, 0o755)
return path
class Arch95Case(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.binary = _write(os.path.join(self.dir, "cloud-hypervisor"))
self.ch_remote = _write(os.path.join(self.dir, "ch-remote"))
self.kernel = _write(os.path.join(self.dir, "vmlinux"))
self.rootfs = _write(os.path.join(self.dir, "disk.raw"), b"R" * 100)
self.ssh_key = _write(os.path.join(self.dir, "id_chv"))
self.kvm = _write(os.path.join(self.dir, "kvm"))
self.cpuinfo = os.path.join(self.dir, "cpuinfo")
with open(self.cpuinfo, "w") as fh:
fh.write("flags : fpu vmx sse\n")
self.state_dir = os.path.join(self.dir, "state")
self.calls = []
self.audit = []
self.api_payloads = []
def tearDown(self):
self.tmp.cleanup()
def _runner(self, cmd, timeout=None):
self.calls.append(list(cmd))
if cmd and cmd[0] == "ssh-keygen":
priv = cmd[cmd.index("-f") + 1]
with open(priv, "w") as fh:
fh.write("-----BEGIN OPENSSH PRIVATE KEY-----\nFAKE\n")
os.chmod(priv, 0o600)
with open(priv + ".pub", "w") as fh:
fh.write("ssh-ed25519 AAAAfake1 vm-key\n")
return FakeResult(0, "", "")
if cmd and cmd[0] == "genisoimage":
out = cmd[cmd.index("-output") + 1]
with open(out, "wb") as fh:
fh.write(b"CIDATA" * 16)
return FakeResult(0, "", "")
return FakeResult(0, "", "")
def _strict_api(self, socket_path, method, path, payload=None):
"""严格替身:vm.create 必须带任务盘 + seed 盘、net 无重复。"""
if path == "/vm.create":
self.api_payloads.append(payload)
disks = [d.get("path") for d in payload.get("disks", [])]
self.assertEqual(len(disks), 2, "VmConfig 必须恰好两盘")
self.assertTrue(disks[0].endswith("disk.raw"),
"第一盘必须是任务盘")
self.assertTrue(disks[1].endswith("seed.iso"),
"第二盘必须是 seed 盘,缺 seed 即假保证")
self.assertTrue(payload.get("kernel", {}).get("path"))
nets = payload.get("net", [])
taps = [n.get("tap") for n in nets]
self.assertEqual(len(taps), len(set(taps)), "net tap 重复")
return (200, {})
def make_exec(self, api=None, runner=None, env_extra=None):
env = {"CHV_EGRESS_PROXY": "http://10.0.0.1:8888",
"CHV_SSH_FORWARD": "1"}
if env_extra:
env.update(env_extra)
ex = CloudHypervisorExecutor(
binary=self.binary, ch_remote=self.ch_remote,
kernel=self.kernel, rootfs=self.rootfs,
ssh_key=self.ssh_key, kvm_path=self.kvm,
cpuinfo_path=self.cpuinfo, state_dir=self.state_dir,
runner=runner or self._runner,
launcher=lambda cmd: FakeProc(alive=True),
api_client=api or self._strict_api,
sleeper=lambda s: None, env=env)
ex.audit_callback = lambda e, t, d, k=None: self.audit.append(
(e, t.get("id") if isinstance(t, dict) else t, d, k))
return ex
class WarmPoolDecisionTest(Arch95Case):
def test_pool_is_experimental_and_dispatch_is_cold_only(self):
self.assertFalse(WarmPool.wired_to_dispatch)
ex = self.make_exec()
pool = WarmPool(ex)
# 手工放一个 warm 条目:dispatch 必须不消耗它(冷启动路径)。
pool._entries["warm-1"] = {"state": "warm",
"task": {"id": "warm-1"},
"vm": "chv-warm-1",
"snapshot_url": None}
ex.wake_vm({"id": "t1"})
self.assertEqual(pool._entries["warm-1"]["state"], "warm")
self.assertEqual(pool.warm_count(), 1)
class StrictVmConfigTest(Arch95Case):
def test_wake_vm_config_has_task_disk_and_seed_disk(self):
ex = self.make_exec()
out = ex.wake_vm({"id": "t1"})
self.assertEqual(out["state"], "running")
self.assertEqual(len(self.api_payloads), 1)
# known_hosts 钉死:文件存在且含预置公钥,SSH 命令带钉死选项。
kh = ex.known_hosts_path({"id": "t1"})
with open(kh) as fh:
content = fh.read()
self.assertIn("AAAAfake1", content)
cmd = ex.build_ssh_command({"id": "t1"}, ["true"])
self.assertIn("StrictHostKeyChecking=yes", " ".join(cmd))
self.assertIn(kh, " ".join(cmd))
def test_strict_ssh_runner_rejects_missing_known_hosts(self):
def strict_runner(cmd, timeout=None):
if cmd and cmd[0] == "ssh":
self.assertIn("StrictHostKeyChecking=yes", cmd)
found = False
for i, a in enumerate(cmd):
if a.startswith("UserKnownHostsFile="):
path = a.split("=", 1)[1]
found = True
elif a == "UserKnownHostsFile":
path = cmd[i + 1]
found = True
else:
continue
with open(path) as fh:
self.assertTrue(fh.read().strip(),
"known_hosts 为空即假钉死")
self.assertTrue(found, "SSH 命令缺 known_hosts 钉死选项")
return self._runner(cmd, timeout=timeout)
ex = self.make_exec(runner=strict_runner)
ex.wake_vm({"id": "t1"})
# 严格 runner 在 _run_guest 里再跑一次 status 风格调用:
# 这里直接验证 build 出的命令能过严格 runner 的检查。
cmd = ex.build_ssh_command({"id": "t1"}, ["true"])
strict_runner(cmd)
class ConfigUnifiedTest(Arch95Case):
def test_preflight_unified_config_invalid(self):
cases = [
("ssh_port", 0), ("ssh_port", 70000),
("max_vms", -1), ("disk_gb", 0),
("api_timeout", 0), ("ready_timeout", -1),
("ready_interval", 0), ("warm_pool_size", -1),
("snapshot_max_count", -1),
("snapshot_max_total_gb", -1),
("snapshot_retention_days", -1),
("host_key_mode", "bogus"),
]
for field, bad in cases:
with self.subTest(field=field, bad=bad):
ex = self.make_exec()
setattr(ex, field, bad)
with self.assertRaises(ChvFailure) as ctx:
ex.preflight()
self.assertEqual(ctx.exception.kind, "config_invalid")
class DockerSyncGuardTest(unittest.TestCase):
"""Docker goal 内联版与正典 parse_goal_result 的同步守卫:
7 个真实标记用例 + 已存在撞名用例,两者状态必须全等;
任一实现改了分叉,测试即 fail(不再只靠文档清单)。"""
def _docker_status(self, stdout, returncode, stderr=""):
old = os.environ.get("XCM_API_KEY")
os.environ["XCM_API_KEY"] = "FAKE-TEST-KEY"
try:
ex = DockerExecutor(runner=lambda cmd, timeout=None:
FakeResult(returncode, stdout, stderr))
ex._call_runner = lambda cmd, timeout=None, env=None: \
FakeResult(returncode, stdout, stderr)
return ex.goal_task({"id": "t1", "goal": "do it",
"goal_max_rounds": 3})["status"]
finally:
if old is None:
del os.environ["XCM_API_KEY"]
else:
os.environ["XCM_API_KEY"] = old
def test_docker_and_canonical_goal_status_equal(self):
cases = [
("goal met after rounds\n", 0, ""),
("goal unmet after rounds\nround 1\nverify_out=fail\n", 0, ""),
("goal interrupted after rounds: boom\n", 1, ""),
("", 1, "boom"),
("KeyError: breakpoint\n", 1, ""),
("", 0, ""),
("goal-resume 拒绝:breakpoint lost\n", 1, ""),
("任务已存在\n", 1, ""),
]
for stdout, rc, stderr in cases:
with self.subTest(stdout=stdout[:30], rc=rc):
canon = parse_goal_result(
"t1", FakeResult(rc, stdout, stderr), "local")
docker = self._docker_status(stdout, rc, stderr)
self.assertEqual(docker, canon["status"])
if __name__ == "__main__":
unittest.main()