146 lines
5.2 KiB
Python
146 lines
5.2 KiB
Python
"""打包进度回调(pack_folder 的 progress 参数)。
|
||||
|
|
|
|||
|
|
约定:
|
|||
|
|
* 每处理一个条目(目录也算)回调一次,``done`` 从 1 递增到 ``total``;
|
|||
|
|
* ``total`` 是完整遍历一遍得到的条目数,为了拿到它多做一次"只 stat"的预扫描,
|
|||
|
|
因此**只有传了回调时**才算,不传回调的路径(CLI)开销一点不变;
|
|||
|
|
* 回调抛出的异常会一路向上抛——GUI 的中断按钮就靠这个语义。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import unittest
|
|||
|
|
|
|||
|
|
import helpers
|
|||
|
|
|
|||
|
|
from sqlite_vfs.core import SQLiteVFS
|
|||
|
|
from sqlite_vfs.folder_packer import FolderPacker
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PackProgressTest(helpers.VFSTestCase):
|
|||
|
|
def setUp(self):
|
|||
|
|
super().setUp()
|
|||
|
|
self.src = helpers.make_tree(
|
|||
|
|
self.path("src"),
|
|||
|
|
{
|
|||
|
|
"a.txt": b"aaa",
|
|||
|
|
"b.txt": b"bbb",
|
|||
|
|
"sub/c.txt": b"ccc",
|
|||
|
|
"sub/deep/d.txt": b"ddd",
|
|||
|
|
"empty_dir/.keep": b"",
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
# 条目数 = 5 个文件(含 empty_dir/.keep)+ 3 个目录(sub / sub/deep / empty_dir)
|
|||
|
|
self.expected_entries = 8
|
|||
|
|
|
|||
|
|
def collect(self, incremental=False):
|
|||
|
|
calls = []
|
|||
|
|
|
|||
|
|
def progress(done, total, rel_path):
|
|||
|
|
calls.append((done, total, rel_path))
|
|||
|
|
|
|||
|
|
vfs = SQLiteVFS(self.path("prog.svfs"), compress=True)
|
|||
|
|
try:
|
|||
|
|
packer = FolderPacker(vfs, fs_name="prog")
|
|||
|
|
with helpers.quiet():
|
|||
|
|
ok = packer.pack_folder(self.src, incremental=incremental, progress=progress)
|
|||
|
|
finally:
|
|||
|
|
vfs.close()
|
|||
|
|
return ok, calls
|
|||
|
|
|
|||
|
|
def test_full_progress_is_one_call_per_entry(self):
|
|||
|
|
ok, calls = self.collect()
|
|||
|
|
self.assertTrue(ok)
|
|||
|
|
self.assertEqual(len(calls), self.expected_entries)
|
|||
|
|
self.assertEqual([done for done, _total, _path in calls], list(range(1, len(calls) + 1)))
|
|||
|
|
|
|||
|
|
def test_total_matches_entry_count(self):
|
|||
|
|
_ok, calls = self.collect()
|
|||
|
|
totals = {total for _done, total, _path in calls}
|
|||
|
|
self.assertEqual(totals, {self.expected_entries})
|
|||
|
|
|
|||
|
|
def test_progress_paths_are_relative_and_normalized(self):
|
|||
|
|
_ok, calls = self.collect()
|
|||
|
|
paths = [path.replace("\\", "/") for _done, _total, path in calls]
|
|||
|
|
self.assertEqual(sorted(paths), sorted(["a.txt", "b.txt", "sub", "sub/c.txt",
|
|||
|
|
"sub/deep", "sub/deep/d.txt",
|
|||
|
|
"empty_dir", "empty_dir/.keep"]))
|
|||
|
|
|
|||
|
|
def test_incremental_progress_counts_all_entries(self):
|
|||
|
|
ok, calls = self.collect(incremental=True)
|
|||
|
|
self.assertTrue(ok)
|
|||
|
|
self.assertEqual(len(calls), self.expected_entries)
|
|||
|
|
self.assertEqual({total for _done, total, _path in calls}, {self.expected_entries})
|
|||
|
|
|
|||
|
|
def test_result_is_same_with_and_without_progress(self):
|
|||
|
|
"""带回调不该改变打包结果:条目集合必须一模一样。"""
|
|||
|
|
db_with = self.path("with.svfs")
|
|||
|
|
db_without = self.path("without.svfs")
|
|||
|
|
|
|||
|
|
vfs = SQLiteVFS(db_with, compress=True)
|
|||
|
|
try:
|
|||
|
|
with helpers.quiet():
|
|||
|
|
FolderPacker(vfs, fs_name="prog").pack_folder(
|
|||
|
|
self.src, progress=lambda *_args: None
|
|||
|
|
)
|
|||
|
|
finally:
|
|||
|
|
vfs.close()
|
|||
|
|
|
|||
|
|
helpers.pack(self.src, db_without, compress=True)
|
|||
|
|
|
|||
|
|
def snapshot(db_path):
|
|||
|
|
vfs = SQLiteVFS(db_path, readonly=True)
|
|||
|
|
try:
|
|||
|
|
return [
|
|||
|
|
(row["path"], row["is_directory"], row["file_size"], row["content_hash"])
|
|||
|
|
for row in vfs.all_entries()
|
|||
|
|
], sorted(row["hash"] for row in vfs.list_blobs())
|
|||
|
|
finally:
|
|||
|
|
vfs.close()
|
|||
|
|
|
|||
|
|
self.assertEqual(snapshot(db_with), snapshot(db_without))
|
|||
|
|
|
|||
|
|
def test_no_precount_when_progress_is_absent(self):
|
|||
|
|
"""不传回调就不该多做那次预扫描(CLI 的开销必须保持原样)。"""
|
|||
|
|
vfs = SQLiteVFS(self.path("nopre.svfs"), compress=True)
|
|||
|
|
try:
|
|||
|
|
packer = FolderPacker(vfs, fs_name="prog")
|
|||
|
|
original = packer._count_entries
|
|||
|
|
called = []
|
|||
|
|
|
|||
|
|
def spy(source_folder):
|
|||
|
|
called.append(source_folder)
|
|||
|
|
return original(source_folder)
|
|||
|
|
|
|||
|
|
packer._count_entries = spy
|
|||
|
|
with helpers.quiet():
|
|||
|
|
self.assertTrue(packer.pack_folder(self.src))
|
|||
|
|
self.assertEqual(called, [])
|
|||
|
|
finally:
|
|||
|
|
vfs.close()
|
|||
|
|
|
|||
|
|
def test_exception_from_callback_propagates(self):
|
|||
|
|
"""回调抛异常要能中断打包——GUI 的"中断"就是靠这个语义实现的。"""
|
|||
|
|
|
|||
|
|
class Stop(Exception):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
seen = []
|
|||
|
|
|
|||
|
|
def progress(done, total, rel_path):
|
|||
|
|
seen.append(rel_path)
|
|||
|
|
if done >= 2:
|
|||
|
|
raise Stop("用户中断")
|
|||
|
|
|
|||
|
|
vfs = SQLiteVFS(self.path("cancel.svfs"), compress=True)
|
|||
|
|
try:
|
|||
|
|
packer = FolderPacker(vfs, fs_name="prog")
|
|||
|
|
with helpers.quiet():
|
|||
|
|
with self.assertRaises(Stop):
|
|||
|
|
packer.pack_folder(self.src, progress=progress)
|
|||
|
|
self.assertEqual(len(seen), 2)
|
|||
|
|
finally:
|
|||
|
|
vfs.close()
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
unittest.main()
|