Python: the rb9704 command line, a fake modem, and the tests
rb9704 drives a 9704 on USB and the Cloudloop account behind it, moved from WII6's host/rockblock with its project names taken out. rb9704-fake speaks JSPR on a pty or a serial port: everything the library asks, MO in segments with the CRC checked, MT, signal, provisioning, refusals and failures on command. The tests run against the fake at every level: the fake itself, the command line, Ground Control's Python binding, the shell sample on native_sim, and the sample on a board (test_zephyr.py, one suite through two consoles). test_real.py makes free checks against a real modem.
This commit is contained in:
1 parent
dbed9bbc88
commit
917522d1f4
12 files changed
+1861
No files matched your search
@@ -0,0 +1,224 @@
|
||||
"""
|
||||
The test harness: a fake modem in a process of its own, and a console to
|
||||
drive whatever is under test.
|
||||
|
||||
pytest the fake and the CLI, and the
|
||||
library's Python binding if
|
||||
rockblock9704 is installed
|
||||
pytest --zephyr-exe build/zephyr/zephyr.exe
|
||||
... and Zephyr on native_sim
|
||||
pytest --board-console /dev/ttyACM0 --board-modem /dev/ttyUSB0
|
||||
... and Zephyr on a board: its
|
||||
shell on one port, its modem
|
||||
UART wired to a USB-serial
|
||||
adapter on the other, where the
|
||||
fake modem answers
|
||||
pytest --real-modem /dev/ttyUSB0 [--zephyr-exe ...]
|
||||
... and the free checks against
|
||||
a real modem: nothing is sent
|
||||
|
||||
The native_sim and board suites are the same tests (test_zephyr.py) run
|
||||
through a different console.
|
||||
"""
|
||||
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
g = parser.getgroup("rb9704")
|
||||
g.addoption("--zephyr-exe", default=os.environ.get("RB9704_ZEPHYR_EXE"),
|
||||
help="the shell sample built for native_sim (zephyr.exe)")
|
||||
g.addoption("--board-console", default=os.environ.get("RB9704_BOARD_CONSOLE"),
|
||||
help="a board's shell, with the shell sample flashed")
|
||||
g.addoption("--board-modem", default=os.environ.get("RB9704_BOARD_MODEM"),
|
||||
help="the host serial port wired to the board's modem UART")
|
||||
g.addoption("--board-baud", type=int, default=115200)
|
||||
g.addoption("--real-modem", default=os.environ.get("RB9704_REAL_MODEM"),
|
||||
help="a real RockBLOCK 9704's port, for the free checks in test_real.py")
|
||||
|
||||
|
||||
class Lines:
|
||||
"""Lines from a stream, collected on a thread, waited on by regex."""
|
||||
|
||||
def __init__(self, stream):
|
||||
self.q = queue.Queue()
|
||||
self.seen = []
|
||||
threading.Thread(target=self._read, args=(stream,), daemon=True).start()
|
||||
|
||||
def _read(self, stream):
|
||||
for line in stream:
|
||||
self.q.put(ANSI.sub("", line).rstrip("\r\n"))
|
||||
|
||||
def expect(self, pattern, timeout=10.0):
|
||||
rx = re.compile(pattern)
|
||||
end = time.monotonic() + timeout
|
||||
while True:
|
||||
for line in self.seen:
|
||||
m = rx.search(line)
|
||||
if m:
|
||||
self.seen = self.seen[self.seen.index(line) + 1:]
|
||||
return m
|
||||
left = end - time.monotonic()
|
||||
if left <= 0:
|
||||
raise AssertionError("no line matching %r in %.0f s; last lines:\n%s"
|
||||
% (pattern, timeout, "\n".join(self.seen[-15:])))
|
||||
try:
|
||||
self.seen.append(self.q.get(timeout=min(left, 0.2)))
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
while not self.q.empty():
|
||||
self.seen.append(self.q.get())
|
||||
self.seen = []
|
||||
|
||||
|
||||
class Fake:
|
||||
"""rb9704-fake in a subprocess, on a pty or on a real port."""
|
||||
|
||||
def __init__(self, port=None, topics="244"):
|
||||
args = [sys.executable, "-m", "rb9704tools.fakemodem", "--quiet", "--topics", topics]
|
||||
if port:
|
||||
args += ["--port", port]
|
||||
self.p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
text=True, bufsize=1,
|
||||
cwd=os.path.dirname(HERE))
|
||||
self.out = Lines(self.p.stdout)
|
||||
self.port = self.out.expect(r"^READY (\S+)").group(1)
|
||||
|
||||
def cmd(self, line):
|
||||
self.p.stdin.write(line + "\n")
|
||||
self.p.stdin.flush()
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.cmd("quit")
|
||||
self.p.wait(timeout=3)
|
||||
except Exception:
|
||||
self.p.kill()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake():
|
||||
f = Fake()
|
||||
yield f
|
||||
f.close()
|
||||
|
||||
|
||||
class NativeConsole:
|
||||
"""The shell sample on native_sim, its modem UART on the fake's pty."""
|
||||
|
||||
def __init__(self, exe, modem_port):
|
||||
self.p = subprocess.Popen([exe, "-uart_stdinout", "-modem_uart_port=" + modem_port],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, text=True, bufsize=1)
|
||||
self.out = Lines(self.p.stdout)
|
||||
# Up when the shell answers; the boot log line can come before the
|
||||
# shell's log backend is there to print it.
|
||||
for _ in range(20):
|
||||
self.sh("rb9704 status")
|
||||
try:
|
||||
self.out.expect(r"powered", timeout=0.5)
|
||||
return
|
||||
except AssertionError:
|
||||
pass
|
||||
self.p.kill()
|
||||
raise AssertionError("native_sim's shell did not answer")
|
||||
|
||||
def sh(self, line):
|
||||
self.out.clear()
|
||||
self.p.stdin.write(line + "\n")
|
||||
self.p.stdin.flush()
|
||||
|
||||
def close(self):
|
||||
self.p.kill()
|
||||
|
||||
|
||||
class SerialConsole:
|
||||
"""The shell sample on a board, over its console port."""
|
||||
|
||||
def __init__(self, port, baud):
|
||||
import serial
|
||||
self.s = serial.Serial(port, baud, timeout=0.1, write_timeout=2)
|
||||
self.out = Lines(self._lines())
|
||||
self.sh("")
|
||||
self.sh("rb9704 end") # a clean start whatever the board was doing
|
||||
|
||||
def _lines(self):
|
||||
buf = b""
|
||||
while True:
|
||||
try:
|
||||
buf += self.s.read(512)
|
||||
except Exception:
|
||||
return
|
||||
while b"\n" in buf:
|
||||
line, buf = buf.split(b"\n", 1)
|
||||
yield line.decode(errors="replace")
|
||||
|
||||
def sh(self, line):
|
||||
self.out.clear()
|
||||
data = (line + "\r").encode()
|
||||
# A shell's receive buffer is small; typed in one burst a long line
|
||||
# loses characters. A person types slower than this.
|
||||
for i in range(0, len(data), 16):
|
||||
self.s.write(data[i:i + 16])
|
||||
time.sleep(0.01)
|
||||
time.sleep(0.2)
|
||||
|
||||
def close(self):
|
||||
self.s.close()
|
||||
|
||||
|
||||
def _targets(config):
|
||||
t = []
|
||||
if config.getoption("--zephyr-exe"):
|
||||
t.append("native_sim")
|
||||
if config.getoption("--board-console"):
|
||||
t.append("board")
|
||||
return t
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "zephyr" in metafunc.fixturenames:
|
||||
targets = _targets(metafunc.config)
|
||||
metafunc.parametrize("zephyr", targets or ["none"], indirect=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def zephyr(request):
|
||||
"""(console, fake): Zephyr under test and the fake modem it talks to."""
|
||||
cfg = request.config
|
||||
if request.param == "none":
|
||||
pytest.skip("no Zephyr target: --zephyr-exe or --board-console")
|
||||
topics = getattr(request, "node").get_closest_marker("topics")
|
||||
topics = topics.args[0] if topics else "244"
|
||||
if request.param == "board" and not cfg.getoption("--board-modem"):
|
||||
pytest.skip("--board-console needs --board-modem")
|
||||
f = Fake(port=cfg.getoption("--board-modem") if request.param == "board" else None,
|
||||
topics=topics)
|
||||
try:
|
||||
if request.param == "native_sim":
|
||||
c = NativeConsole(cfg.getoption("--zephyr-exe"), f.port)
|
||||
else:
|
||||
c = SerialConsole(cfg.getoption("--board-console"), cfg.getoption("--board-baud"))
|
||||
except Exception:
|
||||
f.close()
|
||||
raise
|
||||
yield c, f
|
||||
c.close()
|
||||
f.close()
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line("markers", "topics(ids): the fake modem's provisioning")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""The rb9704 command line against the fake modem."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def run(*args, timeout=30):
|
||||
return subprocess.run([sys.executable, "-m", "rb9704tools.cli", *args], capture_output=True,
|
||||
text=True, timeout=timeout)
|
||||
|
||||
|
||||
def test_jspr_raw(fake):
|
||||
r = run("-p", fake.port, "jspr", "--setup", "GET hwInfo {}")
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert "300434030000000" in r.stdout
|
||||
|
||||
|
||||
def test_provision_sees_topics(fake):
|
||||
r = run("-p", fake.port, "provision", "--timeout", "10")
|
||||
assert r.returncode == 0, r.stdout + r.stderr
|
||||
assert "244" in r.stdout
|
||||
|
||||
|
||||
def test_info_through_the_library(fake):
|
||||
pytest.importorskip("rockblock9704")
|
||||
r = run("-p", fake.port, "info")
|
||||
assert r.returncode == 0, r.stderr
|
||||
assert "300434030000000" in r.stdout
|
||||
|
||||
|
||||
def test_send_asks_first(fake):
|
||||
pytest.importorskip("rockblock9704")
|
||||
r = subprocess.run([sys.executable, "-m", "rb9704tools.cli", "-p", fake.port, "send",
|
||||
"--text", "x"], input="n\n", capture_output=True, text=True, timeout=30)
|
||||
assert "airtime" in (r.stdout + r.stderr).lower()
|
||||
assert not any(l.startswith("MO ") for l in fake.out.seen)
|
||||
|
||||
|
||||
def test_send_with_yes(fake):
|
||||
pytest.importorskip("rockblock9704")
|
||||
r = run("-p", fake.port, "send", "--text", "cli-hello", "--yes")
|
||||
assert r.returncode == 0, r.stdout + r.stderr
|
||||
m = fake.out.expect(r"^MO 244 \d+ (\w+)")
|
||||
assert bytes.fromhex(m.group(1)) == b"cli-hello"
|
||||
@@ -0,0 +1,106 @@
|
||||
"""The fake modem on its own, in this process: no serial port, no library."""
|
||||
|
||||
import base64
|
||||
import json
|
||||
|
||||
from rb9704tools.fakemodem import FakeModem, crc16_xmodem, with_crc
|
||||
|
||||
|
||||
class Wire:
|
||||
def __init__(self, topics=(244,)):
|
||||
self.out = []
|
||||
self.events = []
|
||||
self.m = FakeModem(self.out.append, self.events.append, topics)
|
||||
|
||||
def put(self, line):
|
||||
self.m.feed((line + "\r").encode())
|
||||
|
||||
def lines(self):
|
||||
got = [b.decode().rstrip("\r") for b in self.out]
|
||||
self.out.clear()
|
||||
return [(int(l.split(" ", 2)[0]), l.split(" ", 2)[1], json.loads(l.split(" ", 2)[2]))
|
||||
for l in got]
|
||||
|
||||
|
||||
def test_crc_is_xmodem():
|
||||
assert crc16_xmodem(b"123456789") == 0x31C3
|
||||
assert with_crc(b"hello") == bytes.fromhex("68656c6c6fc362")
|
||||
|
||||
|
||||
def test_begin_sequence():
|
||||
w = Wire()
|
||||
w.put("GET apiVersion {}")
|
||||
w.put("GET simConfig {}")
|
||||
w.put("GET operationalState {}")
|
||||
got = w.lines()
|
||||
assert [(c, t) for c, t, _ in got] == [(200, "apiVersion"), (200, "simConfig"),
|
||||
(200, "operationalState")]
|
||||
assert "active_version" in got[0][2]
|
||||
assert got[1][2]["interface"] == "internal"
|
||||
assert got[2][2]["state"] == "active"
|
||||
|
||||
|
||||
def test_put_sim_config_answers_then_reports_status():
|
||||
w = Wire()
|
||||
w.put('PUT simConfig {"interface": "internal"}')
|
||||
assert [(c, t) for c, t, _ in w.lines()] == [(200, "simConfig"), (299, "simStatus")]
|
||||
|
||||
|
||||
def test_mo_in_segments_with_crc():
|
||||
w = Wire()
|
||||
w.m.segment = 4
|
||||
payload = b"hello world"
|
||||
whole = with_crc(payload)
|
||||
w.put('PUT messageOriginate {"topic_id":244, "message_length":%d, "request_reference":1}'
|
||||
% len(whole))
|
||||
got = w.lines()
|
||||
assert got[0][:2] == (200, "messageOriginate")
|
||||
mid = got[0][2]["message_id"]
|
||||
asks = [got[1]]
|
||||
while True:
|
||||
code, target, body = asks[-1]
|
||||
if target == "messageOriginateStatus":
|
||||
break
|
||||
assert (code, target) == (299, "messageOriginateSegment")
|
||||
s, n = body["segment_start"], body["segment_length"]
|
||||
w.put('PUT messageOriginateSegment {"topic_id":244, "message_id":%d, "segment_length":%d,'
|
||||
' "segment_start":%d, "data":"%s"}'
|
||||
% (mid, n, s, base64.b64encode(whole[s:s + n]).decode()))
|
||||
asks = [l for l in w.lines() if l[0] == 299]
|
||||
assert asks[-1][2]["final_mo_status"] == "mo_ack_received"
|
||||
assert "MO 244 %d %s" % (mid, payload.hex()) in w.events
|
||||
|
||||
|
||||
def test_mo_with_a_bad_crc():
|
||||
w = Wire()
|
||||
bad = b"abc\x00\x00"
|
||||
w.put('PUT messageOriginate {"topic_id":244, "message_length":5, "request_reference":1}')
|
||||
mid = w.lines()[0][2]["message_id"]
|
||||
w.put('PUT messageOriginateSegment {"topic_id":244, "message_id":%d, "segment_length":5,'
|
||||
' "segment_start":0, "data":"%s"}' % (mid, base64.b64encode(bad).decode()))
|
||||
status = [b for c, t, b in w.lines() if t == "messageOriginateStatus"]
|
||||
assert status[0]["final_mo_status"] == "crc_error_in_transfer"
|
||||
assert any(e.startswith("MOBAD 244") for e in w.events)
|
||||
|
||||
|
||||
def test_unprovisioned_topic_is_refused():
|
||||
w = Wire(topics=())
|
||||
w.put('PUT messageOriginate {"topic_id":244, "message_length":7, "request_reference":1}')
|
||||
assert w.lines()[0][:2] == (406, "messageOriginate")
|
||||
|
||||
|
||||
def test_mt_segments_fit_the_library():
|
||||
w = Wire()
|
||||
w.m.send_mt(244, b"y" * 3000)
|
||||
got = w.lines()
|
||||
segs = [b for c, t, b in got if t == "messageTerminateSegment"]
|
||||
assert all(len(s["data"]) <= 1446 for s in segs)
|
||||
data = b"".join(base64.b64decode(s["data"]) for s in segs)
|
||||
assert data == with_crc(b"y" * 3000)
|
||||
assert got[-1][2]["final_mt_status"] == "complete"
|
||||
|
||||
|
||||
def test_unknown_target_is_404():
|
||||
w = Wire()
|
||||
w.put("GET nonsense {}")
|
||||
assert w.lines()[0][:2] == (404, "nonsense")
|
||||
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Ground Control's own Python binding (the rockblock9704 package, the same C
|
||||
library this repository builds into Zephyr) against the fake modem on a pty.
|
||||
The fake runs in a process of its own: the binding holds the GIL through
|
||||
every blocking call.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
rb = pytest.importorskip("rockblock9704")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def modem(fake):
|
||||
m = rb.RockBlock9704()
|
||||
assert m.begin(fake.port)
|
||||
yield m
|
||||
m.end()
|
||||
|
||||
|
||||
def poll_until(m, cond, secs=10):
|
||||
end = time.monotonic() + secs
|
||||
while time.monotonic() < end:
|
||||
m.poll()
|
||||
if cond():
|
||||
return True
|
||||
time.sleep(0.01)
|
||||
return False
|
||||
|
||||
|
||||
def test_identity(modem):
|
||||
assert modem.get_imei() == "300434030000000"
|
||||
assert modem.get_iccid() == "8988169712345678901"
|
||||
assert modem.get_firmware_version() == "v1.2.3"
|
||||
assert modem.get_signal() == 5
|
||||
|
||||
|
||||
def test_send_async(modem, fake):
|
||||
done = []
|
||||
modem.set_mo_message_complete_callback(lambda mid, status: done.append(status))
|
||||
assert modem.send_message_async(b"from-python", 244)
|
||||
assert poll_until(modem, lambda: done)
|
||||
assert done == [1]
|
||||
m = fake.out.expect(r"^MO 244 \d+ (\w+)")
|
||||
assert bytes.fromhex(m.group(1)) == b"from-python"
|
||||
|
||||
|
||||
def test_receive_async(modem, fake):
|
||||
got = []
|
||||
modem.set_mt_message_complete_callback(lambda mid, status: got.append(status))
|
||||
fake.cmd("mt 244 to-python")
|
||||
assert poll_until(modem, lambda: got)
|
||||
assert modem.receive_message_async() == b"to-python"
|
||||
assert modem.acknowledge_receive_head_async()
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
A real RockBLOCK 9704, with --real-modem PORT. Free checks only: identity
|
||||
and signal, never a message, so nothing here costs airtime. Indoors the
|
||||
signal is usually 0, which passes.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from conftest import NativeConsole
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real(request):
|
||||
port = request.config.getoption("--real-modem")
|
||||
if not port:
|
||||
pytest.skip("no --real-modem")
|
||||
return port
|
||||
|
||||
|
||||
def test_library_identity(real):
|
||||
rb = pytest.importorskip("rockblock9704")
|
||||
m = rb.RockBlock9704()
|
||||
assert m.begin(real)
|
||||
try:
|
||||
assert re.fullmatch(r"\d{15}", m.get_imei() or "")
|
||||
assert m.get_card_present()
|
||||
assert 0 <= m.get_signal() <= 5
|
||||
finally:
|
||||
m.end()
|
||||
|
||||
|
||||
def test_zephyr_native_sim_identity(real, request):
|
||||
exe = request.config.getoption("--zephyr-exe")
|
||||
if not exe:
|
||||
pytest.skip("no --zephyr-exe")
|
||||
c = NativeConsole(exe, real)
|
||||
try:
|
||||
c.sh("rb9704 begin")
|
||||
c.out.expect(r"begin: ok", timeout=20)
|
||||
c.sh("rb9704 info")
|
||||
c.out.expect(r"imei\s+\d{15}", timeout=20)
|
||||
c.sh("rb9704 signal")
|
||||
c.out.expect(r"signal: [0-5] bars", timeout=20)
|
||||
c.sh("rb9704 end")
|
||||
c.out.expect(r"end: ok")
|
||||
finally:
|
||||
c.close()
|
||||
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Zephyr, through the shell sample, against the fake modem: on native_sim
|
||||
with --zephyr-exe, on a board with --board-console and --board-modem.
|
||||
"""
|
||||
|
||||
import zlib
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def begin(z):
|
||||
c, f = z
|
||||
c.sh("rb9704 begin")
|
||||
c.out.expect(r"begin: ok")
|
||||
|
||||
|
||||
def test_begin_and_info(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
c.sh("rb9704 info")
|
||||
c.out.expect(r"imei\s+300434030000000")
|
||||
c.out.expect(r"firmware\s+v1\.2\.3")
|
||||
c.out.expect(r"sim\s+card present, connected")
|
||||
|
||||
|
||||
def test_begin_twice_is_already(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
c.sh("rb9704 begin")
|
||||
c.out.expect(r"begin: -\d+ EALREADY")
|
||||
|
||||
|
||||
def test_signal(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
f.cmd("signal 3")
|
||||
c.out.expect(r"signal 3 bars")
|
||||
c.sh("rb9704 signal")
|
||||
c.out.expect(r"signal: 3 bars")
|
||||
|
||||
|
||||
def test_send_reaches_the_network(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
c.sh("rb9704 send 244 hello-network")
|
||||
c.out.expect(r"send: ok")
|
||||
m = f.out.expect(r"^MO 244 (\d+) (\w+)")
|
||||
assert bytes.fromhex(m.group(2)) == b"hello-network"
|
||||
c.out.expect(r"mo done topic 244 id %s result 0" % m.group(1))
|
||||
|
||||
|
||||
def test_send_in_many_segments(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
f.cmd("segment 16")
|
||||
text = "".join(chr(ord("a") + i % 26) for i in range(150))
|
||||
c.sh("rb9704 send 244 " + text)
|
||||
m = f.out.expect(r"^MO 244 \d+ (\w+)")
|
||||
assert bytes.fromhex(m.group(1)).decode() == text
|
||||
c.out.expect(r"result 0")
|
||||
|
||||
|
||||
def test_send_failure_reaches_the_callback(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
f.cmd("mo-fail network_error")
|
||||
c.sh("rb9704 send 244 doomed")
|
||||
c.out.expect(r"mo done topic 244 id \d+ result -\d+")
|
||||
|
||||
|
||||
def test_send_refused_by_the_modem(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
f.cmd("mo-refuse")
|
||||
c.sh("rb9704 send 244 refused")
|
||||
c.out.expect(r"send: -\d+ EIO")
|
||||
|
||||
|
||||
def test_topic_below_64_is_refused_here(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
c.sh("rb9704 send 10 nope")
|
||||
c.out.expect(r"send: -\d+ EINVAL")
|
||||
|
||||
|
||||
@pytest.mark.topics("")
|
||||
def test_provisioning_that_arrives_later_is_seen(zephyr):
|
||||
"""The library keeps the first answer for good; the module asks again."""
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
c.sh("rb9704 send 244 too-early")
|
||||
# EACCES when the library asks first; EIO when it had 244 cached from an
|
||||
# earlier test on the same boot and the modem refused the message.
|
||||
c.out.expect(r"send: -\d+ (EACCES|EIO)")
|
||||
f.cmd("topics 244")
|
||||
c.sh("rb9704 send 244 now-provisioned")
|
||||
c.out.expect(r"send: ok")
|
||||
f.out.expect(r"^MO 244 ")
|
||||
|
||||
|
||||
def test_message_from_the_network(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
f.cmd("mt 244 hello-buoy")
|
||||
c.out.expect(r"mt topic 244 id \d+ len 10 crc %08x: hello-buoy$" % zlib.crc32(b"hello-buoy"))
|
||||
c.sh("rb9704 status")
|
||||
c.out.expect(r"mt\s+1 received")
|
||||
|
||||
|
||||
def test_long_message_from_the_network(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
body = "".join(chr(ord("a") + i % 26) for i in range(1500)) # two MT segments
|
||||
f.cmd("mt 244 " + body)
|
||||
c.out.expect(r"mt topic 244 id \d+ len 1500 crc %08x: %s$"
|
||||
% (zlib.crc32(body.encode()), body[:64]))
|
||||
|
||||
|
||||
def test_end(zephyr):
|
||||
c, f = zephyr
|
||||
begin(zephyr)
|
||||
c.sh("rb9704 end")
|
||||
c.out.expect(r"end: ok")
|
||||
c.sh("rb9704 send 244 after-end")
|
||||
c.out.expect(r"send: -\d+ ENOTCONN")
|
||||
Reference in new issue
Block a user