"""test_pop_quiet — правило громкости и неприкосновенность служебных байт. Скрипт трогает данные, которые потом играет насос на Z80, поэтому тест закрепляет ровно две вещи: формулу (она обязана совпадать с беззнаковым `A/2 + 0x40`, чтобы результат можно было сверять с железной реализацией) и то, что заголовок архива с выравнивающим хвостом остаются как были. Запуск: make test-tools (либо python3 -m unittest discover tools/tests) """ import os import struct import sys import tempfile import unittest from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import pop_quiet as X class Formula(unittest.TestCase): """Таблица громкости: то же, что сделал бы Z80.""" def test_half_is_shift_plus_40(self): t = X.gain_table(0.5) for v in range(256): self.assertEqual(t[v], (v >> 1) + 0x40, f"байт {v:#04x}") def test_silence_stays_silence(self): for gain in (0.5, 0.25, 0.1, 1.0): self.assertEqual(X.gain_table(gain)[X.SILENCE], X.SILENCE) def test_no_clipping_and_range_shrinks(self): t = X.gain_table(0.5) self.assertEqual(t[0x00], 0x40) self.assertEqual(t[0xFF], 0xBF) for v in range(256): self.assertLessEqual(abs(t[v] - X.SILENCE), abs(v - X.SILENCE)) def test_gain_one_is_identity(self): self.assertEqual(X.gain_table(1.0), bytes(range(256))) def test_gain_out_of_range_rejected(self): for bad in (-0.1, 1.5): with self.assertRaises(SystemExit): X.gain_table(bad) def make_arc(bodies): """Собрать PBA1 ровно так, как это делает tools/pop_pack_arc.py.""" raw = bytearray(X.ARC_HEADER) raw[0:4] = X.ARC_MAGIC raw[4] = len(bodies) blob = bytearray() for i, body in enumerate(bodies): off = X.ARC_HEADER + len(blob) struct.pack_into("