test_nextion_display.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import os
  2. import sys
  3. import time
  4. from unittest.mock import MagicMock, Mock, PropertyMock, patch
  5. import pytest
  6. # 添加项目根目录到 Python 路径
  7. sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  8. from modules.nextion_display import EVENT_HEADER, TERMINATOR, NextionDisplay, parse_nextion_events
  9. @pytest.fixture
  10. def display():
  11. return NextionDisplay()
  12. def test_parse_button_event():
  13. data = bytes([EVENT_HEADER, 0x00, 0x05, 0x01]) + TERMINATOR
  14. events, remaining = parse_nextion_events(data)
  15. assert events == [{'type': 'button', 'page': 0, 'comp': 5, 'event': 1}]
  16. assert remaining == b''
  17. def test_parse_multiple_events():
  18. data = (
  19. bytes([EVENT_HEADER, 0x00, 0x05, 0x01]) + TERMINATOR +
  20. bytes([EVENT_HEADER, 0x00, 0x06, 0x01]) + TERMINATOR
  21. )
  22. events, remaining = parse_nextion_events(data)
  23. assert len(events) == 2
  24. assert events[1] == {'type': 'button', 'page': 0, 'comp': 6, 'event': 1}
  25. assert remaining == b''
  26. def test_parse_event_with_trailing_partial_bytes():
  27. data = bytes([EVENT_HEADER, 0x00, 0x05, 0x01]) + TERMINATOR + bytes([EVENT_HEADER])
  28. events, remaining = parse_nextion_events(data)
  29. assert events == [{'type': 'button', 'page': 0, 'comp': 5, 'event': 1}]
  30. assert remaining == bytes([EVENT_HEADER])
  31. def test_encode_nextion_cmd():
  32. display = NextionDisplay()
  33. encoded = display._encode_cmd('t2.txt="终端1"')
  34. assert encoded.endswith(TERMINATOR)
  35. assert encoded.startswith(b't2.txt=')
  36. def test_connect_success(display):
  37. mock_serial = MagicMock()
  38. mock_serial.is_open = True
  39. mock_serial.in_waiting = 0
  40. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial) as mock_cls:
  41. ok, msg = display.connect('/dev/ttyUSB0', 115200)
  42. assert ok is True
  43. assert '115200' in msg
  44. mock_cls.assert_called_once_with('/dev/ttyUSB0', 115200, timeout=0.1)
  45. assert display.connected is True
  46. assert display.port == '/dev/ttyUSB0'
  47. display.disconnect()
  48. def test_connect_failure(display):
  49. with patch('modules.nextion_display.serial.Serial', side_effect=Exception("open fail")):
  50. ok, msg = display.connect('/dev/ttyUSB0', 9600)
  51. assert ok is False
  52. assert 'open fail' in msg
  53. assert display.connected is False
  54. assert display.ser is None
  55. def test_disconnect_when_connected(display):
  56. mock_serial = MagicMock()
  57. mock_serial.is_open = True
  58. mock_serial.in_waiting = 0
  59. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  60. display.connect('/dev/ttyUSB0', 9600)
  61. ok, msg = display.disconnect()
  62. assert ok is True
  63. assert msg == "已断开"
  64. assert display.connected is False
  65. assert display.ser is None
  66. assert display.port is None
  67. assert display.baudrate is None
  68. def test_disconnect_when_not_connected(display):
  69. ok, msg = display.disconnect()
  70. assert ok is True
  71. assert msg == "已断开"
  72. assert display.connected is False
  73. def test_send_cmd_success(display):
  74. mock_serial = MagicMock()
  75. mock_serial.is_open = True
  76. mock_serial.in_waiting = 0
  77. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  78. display.connect('/dev/ttyUSB0', 9600)
  79. ok = display.send_cmd('t2.txt="test"')
  80. assert ok is True
  81. mock_serial.write.assert_called()
  82. display.disconnect()
  83. def test_send_cmd_failure_not_connected(display):
  84. ok = display.send_cmd('t2.txt="test"')
  85. assert ok is False
  86. def test_send_cmd_failure_exception(display):
  87. mock_serial = MagicMock()
  88. mock_serial.is_open = True
  89. mock_serial.in_waiting = 0
  90. mock_serial.write.side_effect = Exception("write fail")
  91. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  92. display.connect('/dev/ttyUSB0', 9600)
  93. ok = display.send_cmd('t2.txt="test"')
  94. assert ok is False
  95. assert display.connected is False
  96. display.disconnect()
  97. def test_send_raw_success(display):
  98. raw = b'\x00\x01\x02' + TERMINATOR
  99. mock_serial = MagicMock()
  100. mock_serial.is_open = True
  101. mock_serial.in_waiting = 0
  102. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  103. display.connect('/dev/ttyUSB0', 9600)
  104. ok = display.send_raw(raw)
  105. assert ok is True
  106. mock_serial.write.assert_called_with(raw)
  107. display.disconnect()
  108. def test_get_status(display):
  109. mock_serial = MagicMock()
  110. mock_serial.is_open = True
  111. mock_serial.in_waiting = 0
  112. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  113. display.connect('/dev/ttyUSB0', 115200)
  114. status = display.get_status()
  115. assert status['connected'] is True
  116. assert status['port'] == '/dev/ttyUSB0'
  117. assert status['baudrate'] == 115200
  118. display.disconnect()
  119. def test_read_loop_dispatches_button_callback(display):
  120. callback = Mock()
  121. display.set_button_callback(callback)
  122. event_bytes = bytes([EVENT_HEADER, 0x00, 0x05, 0x01]) + TERMINATOR
  123. mock_serial = MagicMock()
  124. mock_serial.is_open = True
  125. type(mock_serial).in_waiting = PropertyMock(
  126. side_effect=[len(event_bytes)] + [0] * 100
  127. )
  128. mock_serial.read.return_value = event_bytes
  129. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  130. display.connect('/dev/ttyUSB0', 9600)
  131. time.sleep(0.05)
  132. display.disconnect()
  133. callback.assert_called_once_with(0, 5, 1)
  134. def test_reentrant_connect_no_deadlock(display):
  135. mock_serial = MagicMock()
  136. mock_serial.is_open = True
  137. mock_serial.in_waiting = 0
  138. with patch('modules.nextion_display.serial.Serial', return_value=mock_serial):
  139. display.connect('/dev/ttyUSB0', 9600)
  140. display.connect('/dev/ttyUSB1', 115200)
  141. assert display.port == '/dev/ttyUSB1'
  142. assert display.baudrate == 115200
  143. assert display.connected is True
  144. display.disconnect()