116 lines
4.9 KiB
Python
116 lines
4.9 KiB
Python
import csv
|
|
import json
|
|
from pathlib import Path
|
|
import struct
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
|
|
from logic_analyzer.files import read_capture, ImportCancelled
|
|
|
|
|
|
def run_bytes(length):
|
|
value = length - 1
|
|
if value < 64:
|
|
return bytes([value])
|
|
parts = [value & 127]
|
|
value >>= 7
|
|
while value >= 64:
|
|
parts.append(128 | (value & 127))
|
|
value >>= 7
|
|
return bytes([64 | value] + list(reversed(parts)))
|
|
|
|
|
|
def sal_binary(version=1, initial=1):
|
|
head = b'<SALEAE>' + struct.pack('<IIBdQdBBQ', version, 100, 1, 1000, 0, 0, 0, 0, 2)
|
|
for start, end, state, runs in [(0, 100, initial, [2, 98]), (100, 105, initial ^ 1, [3, 2])]:
|
|
data = b''.join(run_bytes(n) for n in runs)
|
|
if version == 1:
|
|
head += struct.pack('<6Q', start, end, end-start, 1000, 1, len(data)) + data
|
|
head += struct.pack('<QQQI', 1, 0, 0, state)
|
|
else:
|
|
head += struct.pack('<QQBBQ', start, end, state, 0, len(data)) + data
|
|
return head
|
|
|
|
|
|
class LogicFilesTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name)
|
|
|
|
def csv_file(self, text):
|
|
path = self.root / 'capture.csv'
|
|
path.write_text(text, encoding='utf-8-sig')
|
|
return path
|
|
|
|
def test_csv_preserves_irregular_time_and_channel_names(self):
|
|
capture = read_capture(self.csv_file('Time [s],Channel 8,Clock\n127.1,1,0\n127.10000024,0,1\n129,0,1\n'))
|
|
self.assertEqual(capture.start, 127.1)
|
|
self.assertEqual(capture.end, 129)
|
|
self.assertEqual(capture.channels[0].name, 'Channel 8')
|
|
self.assertEqual(list(capture.channels[0].edges), [127.10000024])
|
|
self.assertEqual(capture.channels[0].level_at(127.1), 1)
|
|
self.assertEqual(capture.channels[0].level_at(127.10000024), 0)
|
|
|
|
def test_dsview_csv_metadata_and_units(self):
|
|
capture = read_capture(self.csv_file('; CSV, generated by DSView\n; Sample rate: 100 MHz\nTime(ns), Vin, Vstat\n0,0,1\n240,1,0\n'))
|
|
self.assertEqual(capture.format, 'DSLogic CSV')
|
|
self.assertAlmostEqual(capture.end, 240e-9)
|
|
self.assertEqual(capture.channels[1].name, 'Vstat')
|
|
|
|
def test_csv_rejects_invalid_data(self):
|
|
for rows in ['0,0\n0,1', '1,0\n0,1', 'nan,0', '0,0.5', '0,1,0', '']:
|
|
with self.subTest(rows=rows), self.assertRaises(ValueError):
|
|
read_capture(self.csv_file('Time [s],D0\n' + rows))
|
|
|
|
def test_sal_chunks_levels_and_long_runs(self):
|
|
for version in (1, 3, 4):
|
|
for initial in (0, 1):
|
|
path = self.root / 'capture.sal'
|
|
with zipfile.ZipFile(path, 'w') as archive:
|
|
archive.writestr('meta.json', json.dumps({'data': {'rowsSettings': [
|
|
{'channel': {'type': 'Digital', 'deviceChannel': 4}, 'name': 'Clock'}]}}))
|
|
archive.writestr('digital-4.bin', sal_binary(version, initial))
|
|
capture = read_capture(path)
|
|
self.assertEqual(capture.sample_rate, 1000)
|
|
self.assertEqual(capture.channels[0].name, 'Clock')
|
|
self.assertEqual(capture.channels[0].initial, initial)
|
|
self.assertEqual(list(capture.channels[0].edges), [0.002, 0.103])
|
|
self.assertEqual(capture.end, 0.105)
|
|
|
|
def test_sal_rejects_truncated_and_unknown_version(self):
|
|
for data in (sal_binary()[:-1], sal_binary(77)):
|
|
path = self.root / 'bad.sal'
|
|
with zipfile.ZipFile(path, 'w') as archive:
|
|
archive.writestr('meta.json', '{}')
|
|
archive.writestr('digital-0.bin', data)
|
|
with self.assertRaises(ValueError):
|
|
read_capture(path)
|
|
|
|
def test_dsl_sparse_channels_blocks_and_padding(self):
|
|
path = self.root / 'capture.dsl'
|
|
with zipfile.ZipFile(path, 'w') as archive:
|
|
archive.writestr('header', '[version]\nversion=3\n[header]\ndevice mode=0\n'
|
|
'samplerate=100 MHz\ntotal samples=10\ntotal blocks=2\n'
|
|
'probe3=Vin\nprobe12=Ack\n')
|
|
archive.writestr('L-0/0', bytes([0b11110000]))
|
|
archive.writestr('L-0/1', bytes([0b11111100]))
|
|
archive.writestr('L-1/0', bytes([255]))
|
|
archive.writestr('L-1/1', bytes([255]))
|
|
capture = read_capture(path)
|
|
self.assertEqual([c.name for c in capture.channels], ['Vin', 'Ack'])
|
|
self.assertEqual(list(capture.channels[0].edges), [4e-8, 8e-8])
|
|
self.assertEqual(capture.channels[1].initial, 1)
|
|
self.assertEqual(list(capture.channels[1].edges), [])
|
|
self.assertEqual(capture.end, 1e-7)
|
|
|
|
def test_cancelled_import(self):
|
|
with self.assertRaises(ImportCancelled):
|
|
read_capture(self.csv_file('Time [s],D0\n0,0'), cancel=lambda: True)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|