from decimal import Decimal from pathlib import Path import tempfile import unittest import zipfile from logic_analyzer.csv_import import convert_csv from logic_analyzer.files import ImportCancelled, read_capture class CsvImportTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.source = Path(self.temp.name) / 'сигналы.csv' self.output = Path(self.temp.name) / 'сигналы.dsl' def write(self, value): self.source.write_text(value, encoding='utf-8-sig') def test_regular_samples_preserve_levels_and_names(self): self.write('Time [ns],Vin,Vstat\n0,0,1\n10,1,0\n20,0,1\n') original = self.source.read_bytes() info = convert_csv(self.source, self.output) result = read_capture(self.output) self.assertEqual(info['samplerate'], 100_000_000) self.assertEqual([c.name for c in result.channels], ['Vin', 'Vstat']) self.assertEqual(list(result.channels[0].edges), [1e-8, 2e-8]) self.assertEqual([result.channels[1].level_at(t) for t in [0, 1e-8, 2e-8, 5e-7]], [1, 0, 1, 1]) self.assertEqual(self.source.read_bytes(), original) def test_sparse_transitions_preserve_short_pulses_across_blocks(self): self.write('Time [ns],A\n0,0\n10,1\n650,0\n680,1\n710,0\n') convert_csv(self.source, self.output, block_samples=64) capture = read_capture(self.output) self.assertEqual(list(capture.channels[0].edges), [1e-8, 6.5e-7, 6.8e-7, 7.1e-7]) with zipfile.ZipFile(self.output) as archive: self.assertIn('L-0/1', archive.namelist()) self.assertEqual(len(archive.read('L-0/0')), 8) def test_initial_snapshot_need_not_be_on_edge_clock(self): self.write('Time [s];A\n127,632374014;0\n127,632482000;1\n127,632482400;0\n127,632483000;1\n') info = convert_csv(self.source, self.output) self.assertEqual(info['samplerate'], 5_000_000) self.assertEqual(Decimal(info['origin_seconds']), Decimal('127.632374')) capture = read_capture(self.output) edges = capture.channels[0].edges self.assertAlmostEqual(edges[1] - edges[0], 400e-9, places=14) self.assertAlmostEqual(edges[2] - edges[1], 600e-9, places=14) def test_comments_tab_delimiter_and_negative_origin(self): self.write('; DSView export\n# data\nTime(us)\tКанал\n-1\t1\n0\t0\n1\t1\n') info = convert_csv(self.source, self.output) self.assertEqual(info['origin_seconds'], '-0.000001') self.assertEqual(read_capture(self.output).channels[0].name, 'Канал') def test_bad_csv_leaves_no_output(self): for rows in ('0,0\n1,3.3', '0,0\n0,1', 'nan,0\n1,1', '0,0', '0,0,1\n1,1,0'): with self.subTest(rows=rows): self.write('Time [s],A\n' + rows + '\n') with self.assertRaises(ValueError): convert_csv(self.source, self.output) self.assertFalse(self.output.exists()) def test_output_is_not_overwritten_and_size_limit_precedes_expansion(self): self.write('Time [ns],A\n0,0\n10,1\n100000,0\n') with self.assertRaises(ValueError): convert_csv(self.source, self.output, max_packed_bytes=64) self.output.write_bytes(b'old recording') with self.assertRaises(ValueError): convert_csv(self.source, self.output) self.assertEqual(self.output.read_bytes(), b'old recording') def test_cancel_during_packing_cleans_temporary_output(self): self.write('Time [ns],A\n0,0\n10,1\n100000,0\n') cancelled = False def progress(value): nonlocal cancelled if value > 40: cancelled = True with self.assertRaises(ImportCancelled): convert_csv(self.source, self.output, progress, lambda: cancelled, block_samples=64) self.assertFalse(self.output.exists()) self.assertEqual([p.name for p in Path(self.temp.name).iterdir()], [self.source.name]) def test_changed_source_rejected_before_publication(self): self.write('Time [ns],A\n0,0\n10,1\n20,0\n') changed = False def progress(value): nonlocal changed if value == 99 and not changed: changed = True self.source.write_text('changed', encoding='utf-8') with self.assertRaisesRegex(ValueError, 'изменился'): convert_csv(self.source, self.output, progress) self.assertFalse(self.output.exists())