import pytest
import numpy as np
import json
import tempfile
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from alice_zip.core import (
ALICEZip,
AliceFileHeader,
AliceFileType,
ALICE_MAGIC,
ALICE_VERSION,
ALICE_FINGERPRINT,
)
from alice_zip.generators import (
CompressionEngine,
GeneratorType,
)
class TestAliceFileHeader:
def test_header_to_bytes(self):
header = AliceFileHeader(
file_type=AliceFileType.NUMPY_ARRAY,
engine=CompressionEngine.PROCEDURAL,
original_size=1000,
compressed_size=100,
)
header_bytes = header.to_bytes()
assert header_bytes[:9] == ALICE_MAGIC
assert len(header_bytes) == AliceFileHeader.size()
assert len(header_bytes) == 66
def test_header_from_bytes(self):
header = AliceFileHeader(
version_minor=1, file_type=AliceFileType.NUMPY_ARRAY,
engine=CompressionEngine.PROCEDURAL,
original_size=1000,
compressed_size=100,
)
header_bytes = header.to_bytes()
restored = AliceFileHeader.from_bytes(header_bytes)
assert restored.magic == ALICE_MAGIC
assert restored.version_major == ALICE_VERSION[0]
assert restored.file_type == AliceFileType.NUMPY_ARRAY
assert restored.original_size == 1000
assert restored.compressed_size == 100
def test_header_roundtrip_all_types(self):
for file_type in AliceFileType:
header = AliceFileHeader(
file_type=file_type,
engine=CompressionEngine.PROCEDURAL,
original_size=500,
compressed_size=50
)
header_bytes = header.to_bytes()
restored = AliceFileHeader.from_bytes(header_bytes)
assert restored.file_type == file_type
def test_header_invalid_magic_raises(self):
invalid_data = b'NOT_ALICE' + b'\x00' * 56
with pytest.raises(ValueError, match="Invalid ALICE_ZIP"):
AliceFileHeader.from_bytes(invalid_data)
def test_header_size(self):
assert AliceFileHeader.size() == 66 assert AliceFileHeader.size(version=1) == 65
class TestAliceFileType:
def test_file_type_values(self):
assert AliceFileType.NUMPY_ARRAY.value == 0x01
assert AliceFileType.IMAGE.value == 0x02
assert AliceFileType.AUDIO.value == 0x03
assert AliceFileType.TEXT.value == 0x04
assert AliceFileType.BINARY.value == 0x05
class TestALICEZipCompress:
def setup_method(self):
self.zipper = ALICEZip()
def test_compress_numpy_array(self):
data = np.array([1, 2, 3, 4, 5], dtype=np.float32)
compressed = self.zipper.compress(data)
assert compressed[:9] == ALICE_MAGIC
assert len(compressed) > 0
def test_compress_and_decompress_roundtrip(self):
x = np.arange(100) / 100
data = np.sin(2 * np.pi * 5 * x)
compressed = self.zipper.compress(data)
restored = self.zipper.decompress(compressed)
np.testing.assert_array_almost_equal(data, restored.flatten(), decimal=5)
class TestALICEZipDecompress:
def setup_method(self):
self.zipper = ALICEZip()
def test_decompress_rejects_small_file(self):
small_data = b'TOO_SMALL'
with pytest.raises(ValueError, match="too small"):
self.zipper.decompress(small_data)
def test_decompress_rejects_invalid_magic(self):
fake_data = b'WRONG_MAG' + b'\x00' * 100
with pytest.raises(ValueError, match="Unknown ALICE file format"):
self.zipper.decompress(fake_data)
def test_decompress_texture_format_basic(self):
import json
tex_data = json.dumps({
"magic": "ALICE_TEX",
"version": [1, 0],
"params": {
"texture_type": "solid",
"color": [128, 128, 128],
"width": 64,
"height": 64
}
}).encode('utf-8')
result = self.zipper.decompress(tex_data)
assert result.shape == (64, 64, 3)
assert result.dtype == np.uint8
def test_decompress_valid_generator_payload(self):
payload = {
'type': 'generator',
'params': {
'generator_type': 'polynomial',
'seed': 0,
'parameters': {'coefficients': [1.0, 2.0]}, 'output_shape': [100],
'dtype': 'float32'
}
}
payload_bytes = json.dumps(payload).encode('utf-8')
header = AliceFileHeader(
version_minor=1, file_type=AliceFileType.NUMPY_ARRAY,
engine=CompressionEngine.PROCEDURAL,
original_size=400, compressed_size=len(payload_bytes),
)
data = header.to_bytes() + payload_bytes
result = self.zipper.decompress(data)
assert result.shape == (100,)
assert result.dtype == np.float32
def test_decompress_from_file_path(self):
payload = {
'type': 'generator',
'params': {
'generator_type': 'polynomial',
'seed': 0,
'parameters': {'coefficients': [1.0]}, 'output_shape': [50],
'dtype': 'float32'
}
}
payload_bytes = json.dumps(payload).encode('utf-8')
header = AliceFileHeader(
version_minor=1, file_type=AliceFileType.NUMPY_ARRAY,
engine=CompressionEngine.PROCEDURAL,
original_size=200,
compressed_size=len(payload_bytes),
)
data = header.to_bytes() + payload_bytes
with tempfile.NamedTemporaryFile(suffix='.alice', delete=False) as f:
f.write(data)
temp_path = Path(f.name)
try:
result = self.zipper.decompress(temp_path)
assert result.shape == (50,)
finally:
temp_path.unlink()
class TestALICEZipInfo:
def setup_method(self):
self.zipper = ALICEZip()
def test_info_from_file(self):
payload = {
'type': 'generator',
'params': {
'generator_type': 'polynomial',
'seed': 42,
'parameters': {'coefficients': [1.0, 2.0]},
'output_shape': [100],
'dtype': 'float32'
},
'metadata': {'test_key': 'test_value'}
}
payload_bytes = json.dumps(payload).encode('utf-8')
header = AliceFileHeader(
version_minor=1, file_type=AliceFileType.NUMPY_ARRAY,
engine=CompressionEngine.PROCEDURAL,
original_size=400,
compressed_size=len(payload_bytes),
)
data = header.to_bytes() + payload_bytes
with tempfile.NamedTemporaryFile(suffix='.alice', delete=False) as f:
f.write(data)
temp_path = Path(f.name)
try:
info = self.zipper.info(temp_path)
assert info['magic'] == 'ALICE_ZIP'
assert info['file_type'] == 'NUMPY_ARRAY'
assert info['engine'] == 'procedural_generation'
assert info['original_size'] == 400
assert info['payload_content_type'] == 'generator'
assert 'alice_fingerprint' in info
finally:
temp_path.unlink()
def test_info_texture_format(self):
tex_data = {
'magic': 'ALICE_TEX',
'version': [1, 0],
'params': {
'texture_type': 'perlin_noise',
'width': 256,
'height': 256,
}
}
with tempfile.NamedTemporaryFile(suffix='.alice', delete=False) as f:
f.write(json.dumps(tex_data).encode('utf-8'))
temp_path = Path(f.name)
try:
info = self.zipper.info(temp_path)
assert info['magic'] == 'ALICE_TEX'
assert info['file_type'] == 'TEXTURE'
assert info['texture_type'] == 'perlin_noise'
assert info['dimensions'] == '256x256'
finally:
temp_path.unlink()
class TestConstants:
def test_alice_magic(self):
assert ALICE_MAGIC == b'ALICE_ZIP'
assert len(ALICE_MAGIC) == 9
def test_alice_version(self):
assert isinstance(ALICE_VERSION, tuple)
assert len(ALICE_VERSION) == 2
assert all(isinstance(v, int) for v in ALICE_VERSION)
def test_alice_fingerprint(self):
assert isinstance(ALICE_FINGERPRINT, dict)
assert 'project' in ALICE_FINGERPRINT
assert 'philosophy' in ALICE_FINGERPRINT
assert 'principle' in ALICE_FINGERPRINT
assert ALICE_FINGERPRINT['project'] == 'ALICE-Zip'
assert 'Procedural' in ALICE_FINGERPRINT['philosophy']
assert 'Kolmogorov' in ALICE_FINGERPRINT['principle']
class TestCompressionEngine:
def test_engine_values(self):
assert CompressionEngine.PROCEDURAL.value == "procedural_generation"
assert CompressionEngine.NEURAL_CODEC.value == "neural_codec"
assert CompressionEngine.FUNCTION_FITTER.value == "function_fitter"
assert CompressionEngine.FALLBACK_LZMA.value == "fallback_lzma"
if __name__ == '__main__':
pytest.main([__file__, '-v'])