214 lines
8.0 KiB
Python
214 lines
8.0 KiB
Python
"""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()
|