Skip to content

Commit 9e1be85

Browse files
authored
Merge pull request #30 from bp100a/refactoring-for-testability
Refactoring for testability
2 parents 756ee26 + 73fb3a9 commit 9e1be85

File tree

8 files changed

+165
-36
lines changed

8 files changed

+165
-36
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# CHANGES
2+
## [1.0.3] - 2024-09-06
3+
- refactoring for better test coverage of the Mock USBIP server
24

35
## [1.0.2] - 2024-09-05
46
- refactor some code to increase test coverage of the usbip_client.py

coverage.svg

Lines changed: 3 additions & 3 deletions
Loading

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Repository = "https://github.com/bp100a/serial-usbipclient"
4848
name = "serial-usbipclient"
4949
authors = ["Harry J. Collins <[email protected]>"]
5050
maintainers = ["Harry J. Collins <[email protected]>"]
51-
version = "1.0.2"
51+
version = "1.0.3"
5252
description = "Simple serial connectivity to CDC (serial) USB devices exposed by a USBIPD service."
5353
readme = "README.md"
5454
package-mode = true
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
["serial_usbipclient.tests.test_connection.py.testusbipconnection.test_connection", "serial_usbipclient.tests.test_connection.py.testusbipconnection.test_connection_shutdown", "serial_usbipclient.tests.test_connection.py.testusbipconnection.test_queue_urbs", "serial_usbipclient.tests.test_descriptors.py.testinterfacedescriptor.test_descriptors", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_endpoint_exceptions", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_hardware_id", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_is_complete", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_no_connection", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_oserror_shutdown", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_send_command_connection_error", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_send_unlink", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_valid_endpoints", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_wait_for_unlink", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_mock_usbip_server_connection", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_mock_usbip_server_startup", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_mocked_response", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_reading_paths", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_lsusb_parsing", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_usbip_path", "serial_usbipclient.tests.test_packaging.py.testversion.test_version", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_cmd_response", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_cmd_submit", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_iso_packet_descriptor", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_metastruct_size", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_queue_urb", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_request_devlist", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_sizing", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_urb_endianness", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_configuration", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_delimited_read", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_failed_attachment", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_hardware_id_formatting", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_invalid_response", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_no_read_response", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_no_write_response", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_read_timeout", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_read_write", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_readline", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_readline_timeout", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_restore_connection", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_restore_unattachable_connection", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_restore_unknown_connection", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_timeout_error", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_unknown_device", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_write_string", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_attachment", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_connection", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_gai_error", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_list_published", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_recv_error", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_restore_connection", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_restore_connection_no_device", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_timeout_error", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_configuration_descriptor", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_descriptor_handlers", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_endpoint_descriptor_handlers", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_generate_configuration", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_interface_association_handlers"]
1+
["serial_usbipclient.tests.test_connection.py.testusbipconnection.test_connection", "serial_usbipclient.tests.test_connection.py.testusbipconnection.test_connection_shutdown", "serial_usbipclient.tests.test_connection.py.testusbipconnection.test_queue_urbs", "serial_usbipclient.tests.test_descriptors.py.testinterfacedescriptor.test_descriptors", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_endpoint_exceptions", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_hardware_id", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_is_complete", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_no_connection", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_oserror_shutdown", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_send_command_connection_error", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_send_unlink", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_valid_endpoints", "serial_usbipclient.tests.test_exceptions.py.testexceptions.test_wait_for_unlink", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_mock_usbip_server_connection", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_mock_usbip_server_startup", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_mocked_response", "serial_usbipclient.tests.test_mock_usbip_server.py.testmockusbipserver.test_reading_paths", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_bad_mock_response", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_bad_mock_response_no_device", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_bad_mock_response_with_device", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_bad_urb_response", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_lsusb_parsing", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_server_timeout", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_unlink_no_device", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_urb_read_no_device", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_usbip_path", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_wait_for_response_improper_sockets", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_wait_for_response_new_connection", "serial_usbipclient.tests.test_mock_usbip_server.py.testdeviceconfiguration.test_wait_for_response_wakeup", "serial_usbipclient.tests.test_packaging.py.testversion.test_version", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_cmd_response", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_cmd_submit", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_iso_packet_descriptor", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_metastruct_size", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_queue_urb", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_request_devlist", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_sizing", "serial_usbipclient.tests.test_packet_generation.py.testpacketgeneration.test_urb_endianness", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_configuration", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_delimited_read", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_failed_attachment", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_hardware_id_formatting", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_invalid_response", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_no_read_response", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_no_write_response", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_read_timeout", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_read_write", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_readline", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_readline_timeout", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_restore_connection", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_restore_unattachable_connection", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_restore_unknown_connection", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_timeout_error", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_unknown_device", "serial_usbipclient.tests.test_rw_device.py.testreadwrite.test_write_string", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_attachment", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_connection", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_gai_error", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_list_published", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_recv_error", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_restore_connection", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_restore_connection_no_device", "serial_usbipclient.tests.test_socket_handling.py.testsocketwrapper.test_timeout_error", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_configuration_descriptor", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_descriptor_handlers", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_endpoint_descriptor_handlers", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_generate_configuration", "serial_usbipclient.tests.test_urb_packets.py.testurbpackets.test_interface_association_handlers"]

serial_usbipclient/tests/mock_usbip.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def shutdown(self):
340340
self._far = None
341341

342342

343-
class USBIPClient:
343+
class USBIPServerClient:
344344
"""connections to the usbip clients, wrap the socket to provide more context"""
345345
def __init__(self, connection: socket.socket, address: tuple[str, int], size: int = 0) -> None:
346346
"""local variables"""
@@ -436,7 +436,7 @@ def __init__(self, host: str, port: int):
436436
self.urb_queue: dict[int, Any] = {} # pending read URBs, queued by seq #
437437
self.usb_devices: MockUSBDevice = MockUSBDevice(devices=[]) # to keep mypy & others happy
438438
self._wakeup: Optional[SocketPair] = SocketPair()
439-
self._clients: list[USBIPClient] = []
439+
self._clients: list[USBIPServerClient] = []
440440
self.setup()
441441
if self.host and self.port:
442442
self.event.clear()
@@ -452,6 +452,11 @@ def __init__(self, host: str, port: int):
452452
else:
453453
LOGGER.info("MockUSBIP server not started")
454454

455+
@property
456+
def has_clients(self) -> bool:
457+
"""return the clients we have (for testing)"""
458+
return bool(self._clients)
459+
455460
def setup(self):
456461
"""setup our instance"""
457462
data_path: str = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'usbip_packets.json')
@@ -462,14 +467,21 @@ def setup(self):
462467
self.usb_devices = MockUSBDevice(parser.devices)
463468
self.usb_devices.setup() # now we have binary for the USB devices we can emulate
464469

470+
def wakeup(self):
471+
"""send a wakeup packet"""
472+
if self._wakeup:
473+
self._wakeup.wakeup()
474+
465475
def shutdown(self):
466476
"""shutdown the USBIP server thread"""
467477
if self.thread and self.event.is_set():
468478
self.logger.info("clear event, wait for thread to recognize exit condition")
469479
self.event.clear() # -> 0, thread will exit loop if we aren't blocking on accept()
470480
if self.server_socket:
471-
if not self._is_windows: # in linux-land, need to shut down as well
481+
try:
472482
self.server_socket.shutdown(socket.SHUT_RDWR)
483+
except OSError: # if socket isn't bound yet, an exception is generated
484+
pass
473485
self.server_socket.close() # if we are waiting for accept(), should unblock
474486

475487
self.logger.info("waiting for event to signal (0->1)")
@@ -522,7 +534,7 @@ def generate_mock_response(self, usb: MockDevice, request: CMD_SUBMIT) -> bytes:
522534
self.logger.info("generate_mock_response()")
523535
return response + ret_submit.pack() # send the read values back immediately
524536

525-
def mock_urb_responses(self, client: USBIPClient, message: bytes) -> bytes:
537+
def mock_urb_responses(self, client: USBIPServerClient, message: bytes) -> bytes:
526538
"""return URB packets"""
527539
urb_header: HEADER_BASIC = HEADER_BASIC.unpack(message)
528540
if urb_header.command == BasicCommands.CMD_UNLINK: # de-queue a read request
@@ -604,7 +616,7 @@ def mock_urb_responses(self, client: USBIPClient, message: bytes) -> bytes:
604616
client.busid = bytes()
605617
return failure.pack()
606618

607-
def mock_response(self, client: USBIPClient, message: bytes) -> None:
619+
def mock_response(self, client: USBIPServerClient, message: bytes) -> None:
608620
"""use the lsusb devices to mock a response"""
609621
busid: str = client.busid.strip(b'\0').decode('utf-8') if client.is_attached else 'None'
610622
self.logger.info(f"{busid=}, {message.hex()=}")
@@ -654,15 +666,17 @@ def mock_response(self, client: USBIPClient, message: bytes) -> None:
654666
client.busid = path.busid
655667
self.logger.info(f"OP_REP_IMPORT: {data.hex()}")
656668
return
669+
670+
self.logger.warning("no response")
657671
return
658672

659-
def wait_for_message(self, conn: Optional[USBIPClient] = None) -> tuple[USBIPClient, bytes]:
673+
def wait_for_message(self, conn: Optional[USBIPServerClient] = None) -> tuple[USBIPServerClient, bytes]:
660674
"""wait for a response (or a shutdown)"""
661675
self.logger.info(f"wait_for_message({conn=}), {self._clients=}")
662676
if self._wakeup is None or self.server_socket is None:
663-
raise ValueError("neither the wakeup or server socket can be emptied!")
677+
raise ValueError("neither the wakeup or server socket can be empty!")
664678

665-
rlist: list[socket.socket | USBIPClient] = [self._wakeup.listener, self.server_socket]
679+
rlist: list[socket.socket | USBIPServerClient] = [self._wakeup.listener, self.server_socket]
666680
if conn:
667681
if conn not in self._clients:
668682
self._clients.append(conn)
@@ -677,17 +691,17 @@ def wait_for_message(self, conn: Optional[USBIPClient] = None) -> tuple[USBIPCli
677691
read_sockets, _, _ = select.select(rlist, [], [])
678692
for socket_read in read_sockets:
679693
if socket_read == self._wakeup.listener: # time to bail
680-
self.logger.info("[usbip-server]Wakeup!")
694+
self.logger.info("Wakeup!")
681695
raise OrderlyExit("wakeup!")
682696
elif socket_read == self.server_socket: # someone is knocking
683697
self.logger.info(f"wait_for_message(): accept() {self.server_socket=}")
684698
new_conn, address = self.server_socket.accept() # accept new connection
685-
client: USBIPClient = USBIPClient(connection=new_conn, address=address)
699+
client: USBIPServerClient = USBIPServerClient(connection=new_conn, address=address)
686700
self._clients.append(client)
687701
rlist.append(client)
688702
self.logger.info(f"client @{address} connected {self._clients=}")
689703
continue
690-
elif isinstance(socket_read, USBIPClient):
704+
elif isinstance(socket_read, USBIPServerClient):
691705
# should be a USBClient instance
692706
message: bytes = socket_read.recv(socket_read.size)
693707
self.logger.info(f"wait_for_message: {message.hex()=}")
@@ -700,9 +714,9 @@ def wait_for_message(self, conn: Optional[USBIPClient] = None) -> tuple[USBIPCli
700714
except OSError as os_error:
701715
self.logger.error(f"wait_for_message: OSError: {str(os_error)}")
702716

703-
raise OrderlyExit("wait_for_message(), event set!")
717+
raise OrderlyExit("event set!")
704718

705-
def read_message(self, client: Optional[USBIPClient] = None) -> tuple[USBIPClient, bytes]:
719+
def read_message(self, client: Optional[USBIPServerClient] = None) -> tuple[USBIPServerClient, bytes]:
706720
"""read a single message from the socket"""
707721
client, message = self.wait_for_message(client)
708722
if client.is_attached and message: # reading URBs

serial_usbipclient/tests/test_connection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ def __init__(self, methodName):
2626

2727
def setUp(self):
2828
"""set up our connection test"""
29-
# make sure that each instance of MockUSBIP() has a unique port, even when running in a parallel environment
30-
self.port += self.get_test_index(name=os.path.join(__file__, str(__class__.__name__), self._testMethodName))
3129
super().setUp()
30+
self.port += self.get_test_index(name=os.path.join(__file__, str(__class__.__name__), self._testMethodName))
3231
self.mock_usbip = MockUSBIP(host=self.host, port=self.port)
3332

3433
def test_connection(self):

0 commit comments

Comments
 (0)