Replace progpib.py stub with a proper installable package

Builds a modern, testable API for the Prologix GPIB-ETHERNET controller
(GPIB-USB structurally supported but untested, no hardware available):
Transport abstraction (Ethernet now, serial later), GpibController for
the full "++" command set, a clean-room netfinder discovery client that
fixes a real chr()/struct-format bug present in vendortools/nfutil.py,
and a CLI (discover/config/info/terminal). vendortools/, pty-gpib-emulator/,
sampledata.txt and the manual PDF are kept untouched as reference material.

Depends on the sibling bits package (editable, not yet pinned -- see its
own commit) for MAC formatting and IEEE-488.2 status-byte bit access.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 18:43:08 -05:00
parent 6664760477
commit a9a79b6dd7
19 changed files with 1723 additions and 54 deletions

View File

@@ -0,0 +1,101 @@
import time
from progpib.exceptions import TransportClosed, TransportTimeout
from progpib.transport import EthernetTransport
def test_read_line_reassembles_split_recv(mock_server):
def handler(conn):
conn.sendall(b"hello, wo")
time.sleep(0.05)
conn.sendall(b"rld\n")
time.sleep(0.2)
server = mock_server(handler)
transport = EthernetTransport(server.host, server.port)
transport.open()
try:
line = transport.read_line(timeout=2.0)
assert line == b"hello, world"
finally:
transport.close()
def test_read_line_strips_crlf(mock_server):
def handler(conn):
conn.sendall(b"line one\r\n")
time.sleep(0.2)
server = mock_server(handler)
transport = EthernetTransport(server.host, server.port)
transport.open()
try:
assert transport.read_line(timeout=2.0) == b"line one"
finally:
transport.close()
def test_read_line_times_out_with_no_data(mock_server):
def handler(conn):
time.sleep(0.5)
server = mock_server(handler)
transport = EthernetTransport(server.host, server.port)
transport.open()
try:
try:
transport.read_line(timeout=0.1)
assert False, "expected TransportTimeout"
except TransportTimeout:
pass
finally:
transport.close()
def test_read_until_quiet_stops_after_idle_gap(mock_server):
def handler(conn):
conn.sendall(b"abc")
time.sleep(0.05)
conn.sendall(b"def")
time.sleep(0.5) # long gap -- read_until_quiet should stop before this
conn.sendall(b"ghi")
server = mock_server(handler)
transport = EthernetTransport(server.host, server.port)
transport.open()
try:
data = transport.read_until_quiet(timeout=2.0, idle=0.2)
assert data == b"abcdef"
finally:
transport.close()
def test_read_until_quiet_returns_empty_with_no_data(mock_server):
def handler(conn):
time.sleep(0.3)
server = mock_server(handler)
transport = EthernetTransport(server.host, server.port)
transport.open()
try:
assert transport.read_until_quiet(timeout=0.15) == b""
finally:
transport.close()
def test_recv_raises_closed_on_peer_disconnect(mock_server):
def handler(conn):
conn.close()
server = mock_server(handler)
transport = EthernetTransport(server.host, server.port)
transport.open()
try:
time.sleep(0.1)
try:
transport.read_line(timeout=1.0)
assert False, "expected TransportClosed"
except TransportClosed:
pass
finally:
transport.close()