"""Distinct framing, identity and admission failures in the sample-bit oracle.""" import importlib.util from pathlib import Path import struct import tempfile import tracemalloc import unittest from unittest.mock import patch import zipfile spec = importlib.util.spec_from_file_location( 'signal_oracle', Path(__file__).with_name('signal-oracle.py')) oracle = importlib.util.module_from_spec(spec) spec.loader.exec_module(oracle) class SignalOracleTests(unittest.TestCase): def setUp(self): self.root = tempfile.TemporaryDirectory() self.addCleanup(self.root.cleanup) self.path = Path(self.root.name) / 'sample.sr' def recording(self, data, unit, channels, chunk='logic-1', extra=0): metadata = '[global]\nsigrok version=0.3.0\n[device 1]\ncapturefile=logic-1\n' metadata += f'total probes={len(channels)}\nsamplerate=1 MHz\nunitsize={unit}\n' metadata += ''.join(f'probe{index+1}={name}\n' for index, name in enumerate(channels)) with zipfile.ZipFile(self.path, 'w') as archive: archive.writestr('version', '2') archive.writestr('metadata', metadata) archive.writestr(chunk, data) for index in range(extra): archive.writestr(f'extra-{index}', '') def test_uart_rearms_after_stop_sample_for_a_slightly_faster_sender(self): levels = [1] * 100 for byte in [0x48, 0x65]: for bit in [0, *[(byte >> index) & 1 for index in range(8)], 1]: levels.extend([bit] * 17) levels.extend([1] * 100) data = b''.join(struct.pack('> index) & 1 for index in reversed(range(8))] + [ack] if incomplete and byte == 0x75: values = values[:4] for bit in values: states.extend([(0, bit), (1, bit)]) states.extend([(0, 0), (1, 0), (1, 1)]) return bytes(clock | data << 1 for clock, data in states) def test_i2c_stop_setup_is_distinct_from_another_data_byte(self): self.recording(self.i2c_levels(), 1, ['SCL', 'SDA']) result = oracle.i2c(self.path) self.assertEqual(len(result['reads']), 1) self.assertEqual([row['byte'] for row in result['reads'][0]['bytes']], [0xA5, 0x75]) self.assertFalse(result['reads'][0]['bytes'][-1]['ack']) def test_incomplete_i2c_byte_is_not_invented_at_stop(self): self.recording(self.i2c_levels(incomplete=True), 1, ['SCL', 'SDA']) with self.assertRaisesRegex(ValueError, 'Incomplete I2C byte'): oracle.i2c(self.path) def test_channel_names_must_identify_one_stored_bit(self): self.recording(b'\x00', 1, ['SCL', 'SCL']) with self.assertRaisesRegex(ValueError, 'ambiguous'): oracle.read_edges(self.path, ['SCL']) def test_missing_chunk_is_not_renumbered(self): self.recording(b'\x00', 1, ['SCL', 'SDA'], chunk='logic-1-2') with self.assertRaisesRegex(ValueError, 'chunk order'): oracle.read_edges(self.path, ['SCL', 'SDA']) def test_partial_sample_word_is_rejected(self): self.recording(b'\x00', 2, ['unused', 'MAX3232E DOUT1']) with self.assertRaisesRegex(ValueError, 'incomplete digital samples'): oracle.read_edges(self.path, ['MAX3232E DOUT1']) def test_directory_budget_precedes_zip_object_allocation(self): self.recording(b'\x00', 1, ['SCL', 'SDA'], extra=62) with patch.object(oracle.zipfile, 'ZipFile', side_effect=AssertionError('Too late')): with self.assertRaisesRegex(ValueError, 'excessive ZIP directory'): oracle.read_edges(self.path, ['SCL', 'SDA']) def test_declared_expansion_is_rejected_before_decompression(self): self.recording(b'\x00', 1, ['SCL', 'SDA']) data = bytearray(self.path.read_bytes()) directory = data.index(b'PK\x01\x02') struct.pack_into('