mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-29 01:21:16 -07:00
forgetest: exthost.sender-keep-out, camera.lid-privacy for local viewers, and ctlport_test's keep-out
- suite/extsender.py (new module, so no other test's fingerprint moves): exthost.sender-keep-out. A reference package whose service claims the Grbl sender out as it starts, installed with the sender.keep_out grant (and refused without it), on an idle GRBL-mode machine with no sender connected. It checks the holder and the ext: lease of the extension kind, the host's claim file fresh past forgectrl's grace, a sender from the machine's LAN address turned away with the message and one from the loopback admitted, the operator's release (lease gone, network welcomed, the package reading released), a new claim after a restart, and the stale end with the notice after a disable; everything is put back as found. - camera.lid-privacy: the head camera answers the panel's token and the extension host's header with the lid open, and refuses the cloud client's form (no token, no header) and a stream for no local viewer; the lid camera stays refused to everyone. covers gains src/auth.*. - scripts/bench/ctlport_test.py: test_sender_out on the null-sink controller build (Sender(host=) and closed, own_address(), generation()). Proof: forgetest's unit tests pass; the coverage lint passes against the working trees' file lists. Against the current pins it reports src/senderout.* as selecting nothing until forgectrl's pin moves to the revision that adds it, which lands with this. On the bench reference with the suite mounted: exthost.sender-keep-out PASS (first run found the check racing the host's first write; the test now waits for the file and holds past the grace), and camera.lid-privacy passed every check of its body (its hand-back failed only on the controller gated by the privacy advisory's Revision 5 before it was accepted). ctlport_test.py passes in the driver's CI.
This commit is contained in:
@@ -37,6 +37,14 @@ job after it.
|
||||
one FIRE tick. A jog block carries the modal spindle state, so the
|
||||
stream's mask on jogging is the only thing that keeps a jog dark, and
|
||||
this is what holds the port to it
|
||||
10. "sender out" keeps senders from the network out, and only on an idle
|
||||
machine with a quiet sender: it is refused while a sender line runs
|
||||
(a dwell), within 2 s of the sender's last line, and while a program
|
||||
moves; granted, it drops the sender after a message line, turns every
|
||||
sender from the network away the same way without touching the
|
||||
session, still lets one from this host in, outlives the port's client,
|
||||
and "sender in" ends it. A sender from the network is one that
|
||||
connects to this host's own address rather than the loopback
|
||||
|
||||
Usage: ctlport_test.py [path/to/grblHAL_glowforge]
|
||||
"""
|
||||
@@ -65,14 +73,15 @@ class Sender:
|
||||
"""The scripted sender: every line it receives is classified, and the
|
||||
responses (ok, error:N) are counted apart from everything else."""
|
||||
|
||||
def __init__(self, port, eol="\n"):
|
||||
def __init__(self, port, eol="\n", host="127.0.0.1"):
|
||||
self.eol = eol
|
||||
self.responses = [] # "ok" / "error:N", in order
|
||||
self.other = [] # messages, reports, the banner
|
||||
self.lock = threading.Lock()
|
||||
self.closed = False # the controller closed the socket
|
||||
for _ in range(50):
|
||||
try:
|
||||
self.sock = socket.create_connection(("127.0.0.1", port), timeout=1)
|
||||
self.sock = socket.create_connection((host, port), timeout=1)
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
@@ -92,8 +101,10 @@ class Sender:
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError:
|
||||
self.closed = True
|
||||
return
|
||||
if not data:
|
||||
self.closed = True
|
||||
return
|
||||
buf += data
|
||||
while b"\n" in buf:
|
||||
@@ -633,6 +644,115 @@ def test_dead_man(s):
|
||||
fail("[dead-man] a new client inherited the old jog")
|
||||
|
||||
|
||||
def own_address():
|
||||
"""This host's own address on the network, which a connection to it
|
||||
comes from: a sender from the network, as the controller sees it."""
|
||||
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
u.connect(("192.0.2.1", 9)) # TEST-NET: routes, sends nothing
|
||||
addr = u.getsockname()[0]
|
||||
except OSError:
|
||||
addr = ""
|
||||
finally:
|
||||
u.close()
|
||||
if not addr or addr.startswith("127."):
|
||||
fail("[sender-out] this host has no address but the loopback; the test needs one")
|
||||
return addr
|
||||
|
||||
|
||||
def generation(s):
|
||||
with open(os.path.join(s.workdir, "grbl.state")) as f:
|
||||
return json.load(f)["sender"]["generation"]
|
||||
|
||||
|
||||
def wait_closed(sender, what, timeout=3.0):
|
||||
end = time.time() + timeout
|
||||
while time.time() < end and not sender.closed:
|
||||
time.sleep(0.05)
|
||||
if not sender.closed:
|
||||
fail("[sender-out] %s was not closed" % what)
|
||||
|
||||
|
||||
def test_sender_out():
|
||||
s = Session()
|
||||
try:
|
||||
own = own_address()
|
||||
if s.port.state().get("sender_out") is not False:
|
||||
fail("[sender-out] the state does not say the sender is let in")
|
||||
net = Sender(PORT, host=own) # displaces the session's own sender
|
||||
net.wait_state("Idle")
|
||||
|
||||
# A line that runs: a dwell is Idle to the core, and still the sender's.
|
||||
net.sock.sendall(b"G4 P3" + net.eol.encode())
|
||||
time.sleep(1.0)
|
||||
got = s.port.request("sender out")
|
||||
if got != "busy:sender":
|
||||
fail("[sender-out] during the sender's dwell: %r, wanted busy:sender" % got)
|
||||
end = time.time() + 5
|
||||
while net.count() < 1 and time.time() < end:
|
||||
time.sleep(0.05)
|
||||
# A sender that just finished a line is not done with the machine.
|
||||
if net.send("G21") != "ok":
|
||||
fail("[sender-out] G21 was not answered ok")
|
||||
got = s.port.request("sender out")
|
||||
if got != "busy:sender":
|
||||
fail("[sender-out] within 2 s of the sender's last line: %r, wanted busy:sender" % got)
|
||||
# A program that moves.
|
||||
net.sock.sendall(b"G91 G1 X20 F300" + net.eol.encode())
|
||||
time.sleep(0.8)
|
||||
got = s.port.request("sender out")
|
||||
if got != "busy:state":
|
||||
fail("[sender-out] while a program moves: %r, wanted busy:state" % got)
|
||||
net.wait_state("Idle")
|
||||
print("PASS [sender-out]: refused during a dwell, within 2 s of a line, and while a program moves")
|
||||
|
||||
time.sleep(2.3)
|
||||
gen = generation(s)
|
||||
got = s.port.request("sender out")
|
||||
if got != "ok":
|
||||
fail("[sender-out] an idle machine and a quiet sender: %r, wanted ok" % got)
|
||||
wait_closed(net, "the connected sender")
|
||||
if not net.saw("[MSG:The machine is in use"):
|
||||
fail("[sender-out] the dropped sender was not told why: %r" % net.other[-3:])
|
||||
st = s.port.state()
|
||||
if st.get("sender_out") is not True or st.get("sender") is not False:
|
||||
fail("[sender-out] the state after it: %r" % st)
|
||||
print("PASS [sender-out]: granted, it dropped the sender after a message line")
|
||||
|
||||
gen = generation(s)
|
||||
late = Sender(PORT, host=own)
|
||||
wait_closed(late, "a sender from the network")
|
||||
if not late.saw("[MSG:The machine is in use") or late.count():
|
||||
fail("[sender-out] the turned-away sender read %r" % late.other)
|
||||
time.sleep(0.3)
|
||||
if generation(s) != gen or s.port.state().get("sender") is not False:
|
||||
fail("[sender-out] turning a sender away changed the session")
|
||||
local = Sender(PORT)
|
||||
if local.closed or local.send("G21") != "ok":
|
||||
fail("[sender-out] a sender on this host was not let in")
|
||||
local.close()
|
||||
print("PASS [sender-out]: a sender from the network is turned away and the session is "
|
||||
"untouched; one from this host connects")
|
||||
|
||||
s.port.close()
|
||||
s.port = PortClient(s.path)
|
||||
if s.port.state().get("sender_out") is not True:
|
||||
fail("[sender-out] it did not outlive the port's client")
|
||||
if s.port.request("sender out") != "ok":
|
||||
fail("[sender-out] asked again, it was not ok")
|
||||
if s.port.request("sender in") != "ok":
|
||||
fail("[sender-out] sender in was not ok")
|
||||
if s.port.state().get("sender_out") is not False:
|
||||
fail("[sender-out] sender in did not end it")
|
||||
again = Sender(PORT, host=own)
|
||||
if again.closed or again.send("G21") != "ok":
|
||||
fail("[sender-out] after sender in, a sender from the network was not let in")
|
||||
again.close()
|
||||
print("PASS [sender-out]: it outlives the port's client, and sender in lets the network back in")
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.exists(BIN):
|
||||
fail("no controller binary at %s" % BIN)
|
||||
@@ -653,6 +773,7 @@ def main():
|
||||
test_poll_lf()
|
||||
test_poll_crlf()
|
||||
test_port_dark()
|
||||
test_sender_out()
|
||||
print("ALL PASS")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user