feat(chv-guest): 轻量镜像 + Gitea 双向同步(停全量版,改 Ubuntu Minimal)
- build.sh 改轻量:Ubuntu Minimal 底座、apt 精简(git/ssh/cloud-init/
python3/pip/curl/ca-certificates/xz-utils/fonts-wqy-microhei)、
Playwright + chromium-headless-shell、去 B-16 +12G 扩盘;
加 git + gt 1.0.0(官方 install.sh + SHA256 校验,GITEA_CLI_VERSION
钉 1.0.0)与 chv-sync 启动钩子(cloud-init runcmd)
- B-19(新发现):dpkg freeze 的 ${Package} 会被客机 shell 展开
成空,284 行全空白;加反斜杠转义,探针实测已出真实版本
- buildinfo.py:轻量 contents、chromium-headless-shell、gt_version
- generate/check_lockfile.py:gt/chv-sync 声明来源、headless-shell 映射
- chv-sync.sh(新):boot/pull/push/status,分叉/脏树失败关闭,
凭证走 seed 盘 guest-env,不进镜像
- README §1/§2/§3/§4 改轻量口径,新增 §7 Gitea 双向同步
- tests:ChvSyncTest 6 项(本地 bare 仓当远端);针对性测试 144 项全过
This commit is contained in:
1 parent
9e60d5dfaa
commit
4d09518338
7 files changed
+452
-73
No files matched your search
@@ -447,6 +447,141 @@ class BuildScriptTest(unittest.TestCase):
|
||||
self.assertNotEqual(r.returncode, 0)
|
||||
|
||||
|
||||
# -- (5b) chv-sync 双向同步 ------------------------------------------------
|
||||
# 轻量口径 + Gitea 集成(2026-11-06):本地 bare 仓当远端替身,
|
||||
# 不碰真实 Gitea、不需 token,测同步机制本身(双向通 + 分叉/
|
||||
# 脏树时失败关闭)。boot 的 clone 一步需 root + guest-env,
|
||||
# 不在单元测试里跑,只测 boot 无 guest-env 时静默成功与
|
||||
# guest-env 缺字段时明确失败;真机验收见构建记录。
|
||||
class ChvSyncTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.dir = self.tmp.name
|
||||
self.script = str(Path(__file__).resolve().parents[2]
|
||||
/ "chv-guest" / "chv-sync.sh")
|
||||
self.ws = os.path.join(self.dir, "workspace")
|
||||
os.makedirs(self.ws)
|
||||
# 远端替身:bare 仓 + 一个已推入的初始提交。
|
||||
self.remote = os.path.join(self.dir, "remote.git")
|
||||
subprocess.run(["git", "init", "-q", "--bare", self.remote],
|
||||
check=True, capture_output=True)
|
||||
seed = os.path.join(self.dir, "seed-clone")
|
||||
subprocess.run(["git", "clone", "-q", self.remote, seed],
|
||||
check=True, capture_output=True)
|
||||
self._git(seed, "config", "user.email", "t@t")
|
||||
self._git(seed, "config", "user.name", "t")
|
||||
with open(os.path.join(seed, "a.txt"), "w") as fh:
|
||||
fh.write("v1\n")
|
||||
self._git(seed, "add", "-A")
|
||||
self._git(seed, "commit", "-qm", "v1")
|
||||
self._git(seed, "push", "-q", "origin", "HEAD:main")
|
||||
# 本地工作区:模拟 boot 已 clone 的结果。
|
||||
self.repo = os.path.join(self.ws, "remote")
|
||||
subprocess.run(["git", "clone", "-q", "--branch", "main",
|
||||
self.remote, self.repo],
|
||||
check=True, capture_output=True)
|
||||
self._git(self.repo, "config", "user.email", "t@t")
|
||||
self._git(self.repo, "config", "user.name", "t")
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def _git(self, cwd, *args):
|
||||
return subprocess.run(["git", "-C", cwd] + list(args),
|
||||
check=True, capture_output=True, text=True)
|
||||
|
||||
def _sync(self, *args, env_extra=None):
|
||||
env = dict(os.environ)
|
||||
env["CHV_WORKSPACE"] = self.ws
|
||||
env["CHV_GUEST_ENV"] = os.path.join(self.dir, "no-guest-env")
|
||||
if env_extra:
|
||||
env.update(env_extra)
|
||||
return subprocess.run(["bash", self.script] + list(args),
|
||||
capture_output=True, text=True, env=env)
|
||||
|
||||
def _head(self, cwd):
|
||||
return self._git(cwd, "rev-parse", "HEAD").stdout.strip()
|
||||
|
||||
def test_push_then_pull_round_trip(self):
|
||||
# 本地改 → push → 远端可见。
|
||||
with open(os.path.join(self.repo, "a.txt"), "a") as fh:
|
||||
fh.write("v2-local\n")
|
||||
self._git(self.repo, "add", "-A")
|
||||
self._git(self.repo, "commit", "-qm", "v2-local")
|
||||
r = self._sync("push", self.repo)
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
other = os.path.join(self.dir, "other-clone")
|
||||
subprocess.run(["git", "clone", "-q", "--branch", "main",
|
||||
self.remote, other],
|
||||
check=True, capture_output=True)
|
||||
self.assertEqual(self._head(other), self._head(self.repo))
|
||||
# 远端改 → pull → 本地同步回来。
|
||||
self._git(other, "config", "user.email", "t@t")
|
||||
self._git(other, "config", "user.name", "t")
|
||||
with open(os.path.join(other, "a.txt"), "a") as fh:
|
||||
fh.write("v3-remote\n")
|
||||
self._git(other, "add", "-A")
|
||||
self._git(other, "commit", "-qm", "v3-remote")
|
||||
self._git(other, "push", "-q", "origin", "HEAD:main")
|
||||
r = self._sync("pull", self.repo)
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
with open(os.path.join(self.repo, "a.txt")) as fh:
|
||||
self.assertIn("v3-remote", fh.read())
|
||||
|
||||
def test_push_new_branch_sets_upstream(self):
|
||||
self._git(self.repo, "checkout", "-qb", "feature")
|
||||
with open(os.path.join(self.repo, "b.txt"), "w") as fh:
|
||||
fh.write("f\n")
|
||||
self._git(self.repo, "add", "-A")
|
||||
self._git(self.repo, "commit", "-qm", "feature")
|
||||
r = self._sync("push", self.repo)
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
out = subprocess.run(["git", "--git-dir", self.remote,
|
||||
"branch", "--list", "feature"],
|
||||
check=True, capture_output=True,
|
||||
text=True).stdout
|
||||
self.assertIn("feature", out)
|
||||
|
||||
def test_diverged_pull_fails_closed(self):
|
||||
# 本地与远端同分支各进一步 → pull 必须失败,不自动合并。
|
||||
with open(os.path.join(self.repo, "a.txt"), "a") as fh:
|
||||
fh.write("local-diverge\n")
|
||||
self._git(self.repo, "add", "-A")
|
||||
self._git(self.repo, "commit", "-qm", "local-diverge")
|
||||
other = os.path.join(self.dir, "other2")
|
||||
subprocess.run(["git", "clone", "-q", "--branch", "main",
|
||||
self.remote, other],
|
||||
check=True, capture_output=True)
|
||||
self._git(other, "config", "user.email", "t@t")
|
||||
self._git(other, "config", "user.name", "t")
|
||||
with open(os.path.join(other, "a.txt"), "a") as fh:
|
||||
fh.write("remote-diverge\n")
|
||||
self._git(other, "add", "-A")
|
||||
self._git(other, "commit", "-qm", "remote-diverge")
|
||||
self._git(other, "push", "-q", "origin", "HEAD:main")
|
||||
r = self._sync("pull", self.repo)
|
||||
self.assertNotEqual(r.returncode, 0)
|
||||
with open(os.path.join(self.repo, "a.txt")) as fh:
|
||||
self.assertNotIn("remote-diverge", fh.read())
|
||||
|
||||
def test_dirty_tree_pull_refused(self):
|
||||
with open(os.path.join(self.repo, "a.txt"), "a") as fh:
|
||||
fh.write("uncommitted\n")
|
||||
r = self._sync("pull", self.repo)
|
||||
self.assertNotEqual(r.returncode, 0)
|
||||
|
||||
def test_boot_without_guest_env_is_noop(self):
|
||||
r = self._sync("boot")
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
|
||||
def test_boot_with_incomplete_guest_env_fails(self):
|
||||
env_file = os.path.join(self.dir, "guest-env")
|
||||
with open(env_file, "w") as fh:
|
||||
fh.write("GITEA_URL=https://gitea.tose.top\n")
|
||||
r = self._sync("boot", env_extra={"CHV_GUEST_ENV": env_file})
|
||||
self.assertNotEqual(r.returncode, 0)
|
||||
|
||||
|
||||
# -- (6) 中途死亡 → interrupted → 断点续跑闭环 ---------------------------
|
||||
class MidDeathLoopTest(ChvGapCase):
|
||||
def test_vm_death_is_interrupted_then_breakpoint_resumes(self):
|
||||
|
||||
Reference in new issue
Block a user