feat: SQLiteVFS.add_bytes()/commit()/rename_snapshot()
三处写入侧的缺口,都是被下游"模块银行"(module_bank,把模块打包进 svfs 包 再按需导入)真实逼出来的: - add_bytes(rel_path, data, ...):从内存写文件,不经过磁盘。受保护的字节码、 密文这类绝对不能落盘的内容因此可以直接入库——先写临时文件再 add_file 会把 明文留在磁盘上,而磁盘残留是这类工具最容易被忽视的泄漏点。 - make_parents=True(默认):补齐缺失的父目录行。list_directory 是按 parent_path 过滤的,而"某个目录存在"的唯一标志就是它自己那一行——少一层 目录行,export_directory 从根就递归不下去,export_filesystem() 会静默导出 一个空目录。add_file 一并受益(内部改为读盘后调 add_bytes,行为不变, 含 created_time 取 st_ctime 这个细节)。 - commit=False + 公开的 commit():一组相关文件可以写成一个事务,中途任何一步 失败整批回滚,不会在库里留下半套数据。 - rename_snapshot(id, name):create/list/delete 都有,唯独缺改名;而"把发布名 落在快照上"是版本管理的基本动作。**定格一个版本靠的正是它**:先给当前快照 改名,再另建一个工作快照接手后续写入——直接 create_snapshot(name) 的话, 名字落在会继续被写入的那一版上,下一次改动就把这个"版本"改了。 - DEFAULT_DIR_PERMISSIONS:补出来的目录行用 0o755(表的默认值 0o644 是按文件 定的,套到目录上会让导出的目录不可进入)。 - 版本 0.3.0 → 0.4.0(add_file 开始建父目录行属于可见的行为变化)。 测试:tests/test_core_add_bytes.py(22 条:往返/空内容/bytearray/时间戳与权限/ 压缩/去重/覆盖/空路径/只读拒绝/父目录行与 export 目录树/批量提交与回滚/ add_file 委托行为)+ tests/test_snapshots.py 新增 5 条改名用例(只改名字不动 内容与摘要/空名拒绝/不存在的快照报错/允许重名/改名+派生工作快照之后 已定格的版本不被后续写入改动)。完整测试套件逐个文件跑通。
This commit is contained in:
@@ -122,6 +122,78 @@ class SnapshotLifecycleTest(helpers.VFSTestCase):
|
||||
self.assertEqual([1], [s["id"] for s in vfs.list_snapshots()])
|
||||
self.assertIsNone(vfs.snapshot_id, "当前快照被删后应当需要重新解析")
|
||||
|
||||
def test_rename_snapshot_changes_the_name_only(self):
|
||||
vfs = self._vfs()
|
||||
src = helpers.write_file(self.path("a.txt"), b"a")
|
||||
with helpers.quiet():
|
||||
vfs.add_file(src, "a.txt")
|
||||
before = vfs.compute_digest(store=False)
|
||||
|
||||
self.assertEqual("release-1.0", vfs.rename_snapshot(1, "release-1.0"))
|
||||
self.assertEqual("release-1.0", vfs.get_stats()["snapshot_name"])
|
||||
self.assertEqual(["release-1.0"],
|
||||
[s["name"] for s in vfs.list_snapshots()])
|
||||
self.assertEqual(b"a", vfs.read_file("a.txt"), "改名不该动内容")
|
||||
self.assertEqual(before, vfs.compute_digest(store=False),
|
||||
"改名不该动摘要")
|
||||
|
||||
def test_rename_snapshot_rejects_blank_name(self):
|
||||
vfs = self._vfs()
|
||||
src = helpers.write_file(self.path("a.txt"), b"a")
|
||||
with helpers.quiet():
|
||||
vfs.add_file(src, "a.txt")
|
||||
for blank in ("", " ", None):
|
||||
with self.assertRaises(ValueError):
|
||||
vfs.rename_snapshot(1, blank)
|
||||
|
||||
def test_rename_missing_snapshot_raises(self):
|
||||
"""与 use_snapshot 一致:快照不存在就报 FileNotFoundError。"""
|
||||
vfs = self._vfs()
|
||||
src = helpers.write_file(self.path("a.txt"), b"a")
|
||||
with helpers.quiet():
|
||||
vfs.add_file(src, "a.txt")
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
vfs.rename_snapshot(987654, "nope")
|
||||
|
||||
def test_rename_allows_duplicate_names(self):
|
||||
"""表上没有唯一约束:重名允许(按名字找时由调用方定"取哪个")。"""
|
||||
vfs = self._vfs()
|
||||
src = helpers.write_file(self.path("a.txt"), b"a")
|
||||
with helpers.quiet():
|
||||
vfs.add_file(src, "a.txt")
|
||||
vfs.create_snapshot("v2", copy_entries=True)
|
||||
vfs.rename_snapshot(1, "dup")
|
||||
vfs.rename_snapshot(2, "dup")
|
||||
self.assertEqual(["dup", "dup"],
|
||||
[s["name"] for s in vfs.list_snapshots()])
|
||||
|
||||
def test_rename_then_fork_freezes_the_named_version(self):
|
||||
"""定格一个版本的组合动作:把当前快照改名,再另建一个工作快照接手写入。
|
||||
|
||||
这是"版本号该落在不再变动的那份内容上"的最小保证——不这样做的话,
|
||||
名字会落在**继续被写入**的快照上,下一次改动就把这个版本改了。
|
||||
"""
|
||||
vfs = self._vfs()
|
||||
src = helpers.write_file(self.path("a.txt"), b"v1")
|
||||
with helpers.quiet():
|
||||
vfs.add_file(src, "a.txt")
|
||||
|
||||
vfs.rename_snapshot(vfs._active_snapshot(), "release-1.0")
|
||||
work = vfs.create_snapshot("work", copy_entries=True)
|
||||
|
||||
# 在工作快照里改成 v2,已定格的 release-1.0 必须原封不动
|
||||
src2 = helpers.write_file(self.path("b.txt"), b"v2")
|
||||
with helpers.quiet():
|
||||
vfs.add_file(src2, "a.txt", replace=True)
|
||||
|
||||
self.assertEqual(b"v2", vfs.read_file("a.txt"))
|
||||
release = SQLiteVFS(self.path("s.svfs"), snapshot_id=1)
|
||||
self.addCleanup(release.close)
|
||||
self.assertEqual("release-1.0", release.get_stats()["snapshot_name"])
|
||||
self.assertEqual(b"v1", release.read_file("a.txt"),
|
||||
"定格之后的版本不该被后续写入改动")
|
||||
self.assertEqual(work, 2)
|
||||
|
||||
|
||||
class TimeTravelTest(helpers.VFSTestCase):
|
||||
def _pack_v1_then_v2(self):
|
||||
|
||||
Reference in New Issue
Block a user