fix: make frozen product backup unique and atomic on --force

This commit is contained in:
2026-07-16 05:12:08 -04:00
parent a0c7e043e8
commit 8f349c5c62
2 changed files with 92 additions and 5 deletions
+53
View File
@@ -329,3 +329,56 @@ def test_build_split_force_backs_up_old(tmp_path):
_guard_frozen_products(out_path, manifest_path, force=True)
baks = list(tmp_path.glob("pools.json.bak.*"))
assert len(baks) == 1, f"未备份旧产物: {list(tmp_path.iterdir())}"
def test_build_split_force_twice_same_suffix_keeps_both(tmp_path):
"""连续两次 force 命中同后缀时,第二次备份不覆盖第一次(追加递增序号)。"""
from app.harness.build_split import _guard_frozen_products
out_path = tmp_path / "pools.json"
manifest_path = tmp_path / "split_manifest.json"
# 第一次 force:两文件都无 manifest 里的 pools_sha256 之外的差异,后缀恒为同值。
out_path.write_text('{"gen":1}', encoding="utf-8")
manifest_path.write_text('{"pools_sha256":"deadbeef00000000"}', encoding="utf-8")
_guard_frozen_products(out_path, manifest_path, force=True)
# 第二次 force:写入相同 sha 的新产物,触发同后缀备份。
out_path.write_text('{"gen":2}', encoding="utf-8")
manifest_path.write_text('{"pools_sha256":"deadbeef00000000"}', encoding="utf-8")
_guard_frozen_products(out_path, manifest_path, force=True)
pools_baks = sorted(p.name for p in tmp_path.glob("pools.json.bak.*"))
assert len(pools_baks) == 2, f"同后缀第二次备份覆盖了第一次: {pools_baks}"
def test_build_split_force_rollback_on_partial_failure(tmp_path, monkeypatch):
"""备份第二个文件失败时,第一个已备份文件被 rollback 回原名(原子性)。"""
from pathlib import Path
from app.harness import build_split as bs
out_path = tmp_path / "pools.json"
manifest_path = tmp_path / "split_manifest.json"
out_path.write_text('{"gen":1}', encoding="utf-8")
manifest_path.write_text('{"pools_sha256":"deadbeef00000000"}', encoding="utf-8")
real_rename = Path.rename
calls = {"n": 0}
def flaky_rename(self, target):
# 第一次 rename(备份 pools.json)成功,第二次(备份 manifest)抛错。
calls["n"] += 1
if calls["n"] == 2:
raise OSError("模拟第二个备份 rename 失败")
return real_rename(self, target)
monkeypatch.setattr(Path, "rename", flaky_rename)
with pytest.raises(OSError, match="模拟第二个备份"):
bs._guard_frozen_products(out_path, manifest_path, force=True)
# rollback 后:两原文件仍在原位,无残留 .bak.*
assert out_path.exists(), "第一个文件未被 rollback 回原名"
assert manifest_path.exists(), "第二个文件不应被移动"
assert not list(tmp_path.glob("*.bak.*")), f"残留半备份: {list(tmp_path.iterdir())}"