113 lines
4.8 KiB
Python
113 lines
4.8 KiB
Python
"""压缩算法分发层的直接单测。
|
|||
|
|
|
||
|
|
`compression.py` 是"用哪种算法"的唯一分发点(core 只按 `compress_algo` 值分发,
|
||
|
|
不关心具体算法)。这里锁住它的契约,这样以后往 `ALGORITHMS` 里加算法
|
||
|
|
(包括自定义算法、或未来的加密层)时,有东西能兜住回归。
|
||
|
|
|
||
|
|
分层关系:`huffman.py` 只管纯算法(有自己的 test_huffman.py),
|
||
|
|
`compression.py` 管分发与算法名到数据的映射,`core.py` 管存取。
|
||
|
|
"""
|
||
|
|
|
||
|
|
import unittest
|
||
|
|
import zlib
|
||
|
|
|
||
|
|
import helpers # noqa: F401 -- 把 src 加入 sys.path
|
||
|
|
|
||
|
|
from sqlite_vfs import compression
|
||
|
|
from sqlite_vfs.huffman import FREQ_SIZE
|
||
|
|
|
||
|
|
|
||
|
|
class AlgorithmRegistryTest(unittest.TestCase):
|
||
|
|
def test_default_algorithm_is_zlib(self):
|
||
|
|
self.assertEqual("zlib", compression.DEFAULT_ALGORITHM)
|
||
|
|
|
||
|
|
def test_registry_contains_the_three_builtins(self):
|
||
|
|
self.assertEqual(("none", "zlib", "huffman"), compression.ALGORITHMS)
|
||
|
|
|
||
|
|
def test_every_registered_algorithm_roundtrips(self):
|
||
|
|
data = b"the quick brown fox jumps over the lazy dog. " * 60
|
||
|
|
for algorithm in compression.ALGORITHMS:
|
||
|
|
with self.subTest(algorithm=algorithm):
|
||
|
|
algo, freq, blob = compression.compress_with(data, algorithm)
|
||
|
|
self.assertEqual(algorithm, algo)
|
||
|
|
self.assertEqual(data, compression.decompress_with(algo, freq, blob))
|
||
|
|
|
||
|
|
def test_unknown_algorithm_is_rejected_on_both_sides(self):
|
||
|
|
with self.assertRaises(ValueError) as ctx:
|
||
|
|
compression.compress_with(b"x", "lzma")
|
||
|
|
self.assertIn("lzma", str(ctx.exception))
|
||
|
|
self.assertIn("none", str(ctx.exception), "报错要列出可用算法")
|
||
|
|
|
||
|
|
with self.assertRaises(ValueError):
|
||
|
|
compression.decompress_with("lzma", None, b"x")
|
||
|
|
|
||
|
|
|
||
|
|
class CompressContractTest(unittest.TestCase):
|
||
|
|
DATA = b"payload " * 100
|
||
|
|
|
||
|
|
def test_none_returns_the_input_unchanged(self):
|
||
|
|
algo, freq, blob = compression.compress_with(self.DATA, "none")
|
||
|
|
self.assertEqual("none", algo)
|
||
|
|
self.assertIsNone(freq)
|
||
|
|
self.assertIs(self.DATA, blob, "none 应当零拷贝直接返回原对象")
|
||
|
|
|
||
|
|
def test_zlib_produces_a_real_zlib_stream(self):
|
||
|
|
algo, freq, blob = compression.compress_with(self.DATA, "zlib")
|
||
|
|
self.assertIsNone(freq, "zlib 不需要附加数据")
|
||
|
|
self.assertEqual(self.DATA, zlib.decompress(blob))
|
||
|
|
self.assertLess(len(blob), len(self.DATA))
|
||
|
|
|
||
|
|
def test_huffman_carries_a_fixed_size_frequency_table(self):
|
||
|
|
algo, freq, blob = compression.compress_with(self.DATA, "huffman")
|
||
|
|
self.assertEqual("huffman", algo)
|
||
|
|
self.assertEqual(FREQ_SIZE, len(freq), "频率表固定 1024 字节")
|
||
|
|
self.assertEqual(self.DATA, compression.decompress_with(algo, freq, blob))
|
||
|
|
|
||
|
|
def test_huffman_without_frequency_table_is_rejected(self):
|
||
|
|
algo, _freq, blob = compression.compress_with(self.DATA, "huffman")
|
||
|
|
with self.assertRaises(ValueError) as ctx:
|
||
|
|
compression.decompress_with(algo, None, blob)
|
||
|
|
self.assertIn("频率表", str(ctx.exception))
|
||
|
|
|
||
|
|
def test_stored_size_counts_the_frequency_table(self):
|
||
|
|
_algo, freq, blob = compression.compress_with(self.DATA, "huffman")
|
||
|
|
self.assertEqual(
|
||
|
|
len(blob) + len(freq), compression.stored_size(blob, freq)
|
||
|
|
)
|
||
|
|
self.assertEqual(len(blob), compression.stored_size(blob, None))
|
||
|
|
|
||
|
|
def test_stored_size_ignores_an_empty_frequency_table(self):
|
||
|
|
self.assertEqual(3, compression.stored_size(b"abc", b""))
|
||
|
|
|
||
|
|
|
||
|
|
class AlgorithmTradeoffTest(unittest.TestCase):
|
||
|
|
"""把实测出来的取舍钉在测试里:默认算法不能悄悄退化成更差的那个。"""
|
||
|
|
|
||
|
|
#: 高度重复的文本——LZ77 的强项,纯熵编码的弱项
|
||
|
|
REPETITIVE = b"function render(el){return el.innerHTML}\n" * 800
|
||
|
|
|
||
|
|
def test_zlib_beats_huffman_by_a_lot_on_repetitive_text(self):
|
||
|
|
_a1, f1, z = compression.compress_with(self.REPETITIVE, "zlib")
|
||
|
|
_a2, f2, h = compression.compress_with(self.REPETITIVE, "huffman")
|
||
|
|
zlib_total = compression.stored_size(z, f1)
|
||
|
|
huffman_total = compression.stored_size(h, f2)
|
||
|
|
self.assertLess(
|
||
|
|
zlib_total * 10,
|
||
|
|
huffman_total,
|
||
|
|
f"zlib={zlib_total} huffman={huffman_total}——差距应当在一个数量级上",
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_huffman_is_not_used_for_small_files(self):
|
||
|
|
"""huffman 每个文件要带 1024 字节频率表,小文件必然负收益。"""
|
||
|
|
small = b"tiny content"
|
||
|
|
_algo, freq, blob = compression.compress_with(small, "huffman")
|
||
|
|
self.assertGreater(
|
||
|
|
compression.stored_size(blob, freq),
|
||
|
|
len(small),
|
||
|
|
"小文件压完更大——这正是 core 用 COMPRESS_MIN_SIZE 挡住它的原因",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|