Skip to content

Commit 035834b

Browse files
committed
Update test
1 parent b88b324 commit 035834b

1 file changed

Lines changed: 61 additions & 11 deletions

File tree

tests/test_connection.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from collections.abc import Generator
22
from contextlib import contextmanager
3+
import threading
34
import time
45
from unittest import mock
56

67
from mock_serial import MockSerial # type: ignore[import]
78
import pytest
9+
import serial
810

911
from ynca.connection import YncaConnection, YncaProtocolStatus
1012
from ynca.errors import YncaConnectionError, YncaConnectionFailed
@@ -82,21 +84,69 @@ def test_connect_runtime_error(mock_serial: MockSerial) -> None:
8284

8385

8486
def test_disconnect() -> None:
85-
mock_serial = MockSerial()
86-
mock_serial.open()
87+
disconnect_event = threading.Event()
88+
disconnect_callback = mock.MagicMock(side_effect=lambda: disconnect_event.set())
8789

88-
mock_serial.stub(
89-
receive_bytes=b"@SYS:MODELNAME=?\r\n",
90-
send_bytes=b"@SYS:MODELNAME=TESTMODEL\r\n",
91-
) # Keep alive
90+
read_should_fail = threading.Event()
9291

93-
disconnect_callback = mock.MagicMock()
92+
mock_ser = mock.MagicMock()
93+
mock_ser.is_open = True
94+
mock_ser.in_waiting = 0
9495

95-
connection = YncaConnection(mock_serial.port)
96-
connection.connect(disconnect_callback=disconnect_callback)
96+
def controlled_read(_size: int) -> bytes:
97+
# Block until the test triggers a simulated disconnect, just like a real
98+
# serial port would block until data arrives or the connection drops.
99+
read_should_fail.wait(timeout=5.0)
100+
msg = "Simulated unexpected disconnect"
101+
raise serial.SerialException(msg)
97102

98-
mock_serial.close()
99-
time.sleep(SHORT_DELAY)
103+
mock_ser.read.side_effect = controlled_read
104+
105+
with mock.patch("serial.serial_for_url", return_value=mock_ser):
106+
connection = YncaConnection("dummy_port")
107+
connection.connect(disconnect_callback=disconnect_callback)
108+
109+
# Trigger the simulated disconnect; controlled_read raises SerialException
110+
# which the ReaderThread detects and routes to connection_lost().
111+
read_should_fail.set()
112+
113+
assert disconnect_event.wait(
114+
timeout=2.0
115+
), "Disconnect callback not called within timeout"
116+
117+
assert disconnect_callback.call_count == 1
118+
119+
120+
def test_disconnect_oserror() -> None:
121+
"""OSError from in_waiting (e.g. EIO on PTY/USB disconnect) must still fire the callback.
122+
123+
pyserial's ReaderThread only catches serial.SerialException inside its read
124+
loop, so an OSError raised by in_waiting escapes without calling
125+
connection_lost(). _ReaderThread wraps this case.
126+
"""
127+
disconnect_event = threading.Event()
128+
disconnect_callback = mock.MagicMock(side_effect=lambda: disconnect_event.set())
129+
130+
in_waiting_should_fail = threading.Event()
131+
132+
mock_ser = mock.MagicMock()
133+
mock_ser.is_open = True
134+
135+
def controlled_in_waiting() -> int:
136+
in_waiting_should_fail.wait(timeout=5.0)
137+
raise OSError(5, "Input/output error")
138+
139+
type(mock_ser).in_waiting = mock.PropertyMock(side_effect=controlled_in_waiting)
140+
141+
with mock.patch("serial.serial_for_url", return_value=mock_ser):
142+
connection = YncaConnection("dummy_port")
143+
connection.connect(disconnect_callback=disconnect_callback)
144+
145+
in_waiting_should_fail.set()
146+
147+
assert disconnect_event.wait(
148+
timeout=2.0
149+
), "Disconnect callback not called within timeout"
100150

101151
assert disconnect_callback.call_count == 1
102152

0 commit comments

Comments
 (0)