"""The reusable terminal must work without a Tms2812Tab or its private state.""" import os from pathlib import Path import subprocess import sys os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") import pytest from PySide6.QtTest import QTest from PySide6.QtWidgets import QApplication, QTabWidget from pysetterminal.panel import ByteWorkPanel, build_byte_page class Protocol: def __init__(self): self.requests = [] self.callback = None self.cancelled = 0 def request_async(self, address, value, controller, callback): self.requests.append((address, value, controller)) self.callback = callback def reply(self, success, result): callback, self.callback = self.callback, None callback(success, result) def cancel_async(self): self.cancelled += 1 if self.callback is not None: self.reply(False, "cancelled") def poke(self, address, value, controller): self.requests.append((address, value, controller)) return True, {"raw": b"ack"} def peek(self, address, controller): self.requests.append((address, None, controller)) return True, {"value": 0x1234, "raw_response": {"raw": b"reply"}} def send_raw(self, text): return True, text def receive_raw(self): return False, "Нет данных" @pytest.fixture(scope="module") def app(): return QApplication.instance() or QApplication([]) @pytest.fixture def page(app, tmp_path): protocol = Protocol() widget = ByteWorkPanel(protocol, device_address=7, macros_file=tmp_path / "macros.json") yield widget, protocol widget.close() app.processEvents() def test_shared_panel_imports_and_constructs_without_desktop_application(tmp_path): templates = Path(__file__).resolve().parents[1] code = """ import importlib.abc, sys class BlockDesktop(importlib.abc.MetaPathFinder): def find_spec(self, fullname, path=None, target=None): if fullname.split('.')[0] == 'gui_desktop': raise ImportError('Unexpected desktop dependency') sys.meta_path.insert(0, BlockDesktop()) from PySide6.QtWidgets import QApplication, QTabWidget from pysetterminal.panel import build_byte_page from pysetterminal.rawprotocol import RawProtocol app = QApplication([]) tabs = QTabWidget() page = build_byte_page(RawProtocol(), macros_file='macros.json') tabs.addTab(page, 'Работа с байтом') assert tabs.widget(0) is page assert 'gui_desktop' not in sys.modules assert page.bus_widget and page.raw_widget page.close() """ result = subprocess.run([sys.executable, "-c", code], cwd=tmp_path, env=dict(os.environ, PYTHONPATH=str(templates)), capture_output=True, text=True, timeout=30) assert result.returncode == 0, result.stderr def test_multiple_tabs_keep_controls_status_and_macros_separate(app, tmp_path): calls = [] tabs = QTabWidget() first = build_byte_page(Protocol(), start_word=lambda *args: calls.append(args), macros_file=tmp_path / "first.json") second = ByteWorkPanel(Protocol(), macros_file=tmp_path / "second.json") tabs.addTab(first, "Первый") tabs.addTab(second, "Второй") first.byte_write_value.setText("0301") first.byte_write_value.textEdited.emit("0301") first.byte_write_address.setText("0x1234") first.byte_write_button.click() first.set_read_value(0x1234, 0x0301) first.set_status("Готово") first.bus_widget.macros = [{"name": "Первый", "commands": []}] first.bus_widget.save_macros() assert calls == [(True, 0x1234, 0x0301)] assert first.byte_write_binary.text() == "0000 0011 0000 0001" assert second.byte_write_value.text() == "0000" assert second.byte_read_value.text() == "" assert second.status.text() == "Нет данных от контроллера" second.bus_widget.load_macros() assert second.bus_widget.macros == [] first.close() second.close() tabs.close() @pytest.mark.parametrize("address", ["4096", "$1000", "0x1000"]) def test_read_accepts_address_formats_and_displays_both_values(page, address): widget, protocol = page widget.byte_read_address.setText(address) widget.byte_read_button.click() assert protocol.requests == [(0x1000, None, 7)] assert not widget.byte_read_button.isEnabled() protocol.reply(True, {"value": 0xABCD}) assert widget.byte_read_address.text() == "$1000" assert widget.byte_read_value.text() == "ABCD" assert widget.byte_read_binary.text() == "1010 1011 1100 1101" assert widget.byte_read_button.isEnabled() @pytest.mark.parametrize("address,value,binary", [ ("-1", "0000", "0"), ("$100000000", "0000", "0"), ("invalid", "0000", "0"), ("$1000", "10000", "10000000000000000"), ("$1000", "0001", "0"), ("$1000", "GGGG", "0"), ("$1000", "0000", "invalid"), ]) def test_invalid_inputs_do_not_send(page, address, value, binary): widget, protocol = page widget.byte_write_address.setText(address) widget.byte_write_value.setText(value) widget.byte_write_binary.setText(binary) widget.byte_write_button.click() assert protocol.requests == [] assert "Проверьте" in widget.status.text() @pytest.mark.parametrize("readback,confirmed", [(0x0301, True), (0x0302, False)]) def test_write_is_verified_and_controller_is_captured_for_whole_operation(page, readback, confirmed): widget, protocol = page controller = [13] widget.device_address = lambda: controller[0] widget.byte_write_value.setText("0301") widget.sync_byte_value("0301", 16) widget.byte_write_button.click() assert protocol.requests == [(0x1000, 0x0301, 13)] controller[0] = 21 protocol.reply(True, {"raw": b"ack"}) assert protocol.requests[-1] == (0x1000, None, 13) protocol.reply(True, {"value": readback}) assert widget.byte_read_value.text() == f"{readback:04X}" assert ("Запись проверена" in widget.status.text()) == confirmed if not confirmed: assert "не подтверждена" in widget.status.text() assert widget.byte_write_button.isEnabled() def test_failure_and_cancellation_restore_controls_and_ignore_late_reply(page): widget, protocol = page widget.byte_read_button.click() protocol.reply(False, "CRC ответа не совпадает") assert widget.status.text() == "CRC ответа не совпадает" assert widget.byte_read_button.isEnabled() widget.byte_read_button.click() late_reply = protocol.callback widget.stop() assert protocol.cancelled == 1 widget.byte_read_button.click() late_reply(True, {"value": 0x9999}) assert widget.byte_read_value.text() == "" assert not widget.byte_read_button.isEnabled() protocol.reply(True, {"value": 0x1234}) assert widget.byte_read_value.text() == "1234" assert widget.byte_read_button.isEnabled() def test_controller_provider_is_used_by_bus_raw_memory_and_macros(app, tmp_path): protocol = Protocol() controller = [21] widget = ByteWorkPanel(protocol, device_address=lambda: controller[0], macros_file=tmp_path / "address.json") try: widget.raw_widget.memory_write() widget.raw_widget.memory_read() assert protocol.requests == [(0x2000, 0, 21), (0x2000, None, 21)] widget.bus_widget.read_from_bus() assert protocol.requests[-2:] == [(0x200B, 0, 21), (0x200F, None, 21)] widget.bus_widget.execute_macro_commands([{"type": "read", "tetrad": 3, "register": 5}]) controller[0] = 22 QTest.qWait(20) assert protocol.requests[-1] == (0x200B, 0x35, 21) protocol.reply(True, {"raw": b"ack"}) QTest.qWait(20) assert protocol.requests[-1] == (0x200F, None, 21) protocol.reply(True, {"value": 0x4321, "raw_response": {"raw": b"reply"}}) QTest.qWait(20) assert not widget.bus_widget._macro_running assert widget.bus_widget.bus_read_value_label.text() == "0x4321 (17185)" finally: widget.close()