aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-17 13:38:15 +0200
committeruser <user@clank>2026-08-17 13:38:15 +0200
commitea39e09a52240ce1d5166e9137e39b7caad499fd (patch)
tree8f841f2ff3f14abce054136ac9a4cf3fe47162fe /tests
parentsl0ppty: a skill for driving it, and the two variables that made one possible (diff)
downloadslosh-ea39e09a52240ce1d5166e9137e39b7caad499fd.tar.gz
slosh-ea39e09a52240ce1d5166e9137e39b7caad499fd.tar.xz
slosh-ea39e09a52240ce1d5166e9137e39b7caad499fd.zip
sl0ppty: a --script pane is told no session rather than somebody else's
`server.c` set `SL0PPTY_SESSION` for its panes and carried a comment saying the value was "deliberately unset under `--script`". Nothing unset it. A value never set is a value inherited, so a script driver started from inside a pane handed its own panes the *outer* session's name -- and the recipe the skill documents, `-s ${SL0PPTY_SESSION:-main} cmd`, then sent its commands to a session it was not in. Not a fallback to a guess: a confident wrong answer, because the name it read was a real session that really answers. Both variables now go through one `session_env()`, called by the server with its name and by the headless driver with NULL, because the two modes have to agree about what a pane is told and a comment in one of them is not agreement. `SL0PPTY_BIN` is set there too rather than inherited, so it names the binary that actually made the pane instead of one that made some pane somewhere -- which is the whole reason that variable exists. Found while explaining a stale pane title, from a check in `test_skill.py` that failed under `make test` and passed on its own. It was not flaky: the suite runs from inside a pane, where these are set, and the tool I ran it from by hand had an environment where they were not. A test whose answer depends on where you run it looks exactly like a race. So it now passes the lie in deliberately: the session under test is started with `SL0PPTY_SESSION=somebody-elses-session`, which makes the difference between "never set" and "cleared" visible from any shell. The mirror check is next to it -- a real session tells its panes its *own* name, even when started from inside another one -- so the obvious wrong fix, unsetting it in both modes, fails. Both were verified by making both wrong versions and watching each test bite. The two title checks now wait for the line instead of sampling after 120ms. That was not the bug (a shell prints in about 12ms here, even under disk churn) and it is one assumption fewer.
Diffstat (limited to 'tests')
-rwxr-xr-xtests/test_skill.py62
1 files changed, 50 insertions, 12 deletions
diff --git a/tests/test_skill.py b/tests/test_skill.py
index 5bf348b..edb3303 100755
--- a/tests/test_skill.py
+++ b/tests/test_skill.py
@@ -14,6 +14,8 @@ import os
import re
import subprocess
import sys
+import tempfile
+import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from harness import BIN, Session, check, report
@@ -70,23 +72,59 @@ def test_the_environment_it_promises_is_the_environment_panes_get():
for var in ("SL0PPTY", "SL0PPTY_SESSION", "SL0PPTY_BIN"):
check("the skill documents " + var, var in body, "missing " + var)
- # SL0PPTY is set by pty.c for every pane, in every mode.
- with Session(["/bin/sh", "-c", 'echo "V=[$SL0PPTY]"; read x']) as s:
- s.settle(120)
- check("a pane really is told it is in one",
- "V=[1]" in s.snapshot().screen(), s.snapshot().screen())
-
- # The other two are set by the server, which --script is not. The skill says
- # so; this is that sentence being true.
- with Session(["/bin/sh", "-c", 'echo "S=[$SL0PPTY_SESSION]"; read x']) as s:
- s.settle(120)
- check("and that --script has no session to name",
- "S=[]" in s.snapshot().screen(), s.snapshot().screen())
+ # Started from an environment that already names a session and a binary,
+ # because that is the case the claim is *about*. This suite normally runs
+ # inside a pane, where both are set, so a driver that merely never sets them
+ # is indistinguishable from one that clears them -- until it hands its own
+ # panes the outer session's name and a script in there sends its commands
+ # somewhere else. Passing the lie in makes the difference visible from any
+ # shell, rather than only from one that happens to have them set.
+ lie = {"SL0PPTY_SESSION": "somebody-elses-session", "SL0PPTY_BIN": "/nope/x"}
+ probe = ["/bin/sh", "-c",
+ 'echo "V=[$SL0PPTY] S=[$SL0PPTY_SESSION] B=[$SL0PPTY_BIN]"; read x']
+ with Session(probe, cols=120, rows=8, env=lie) as s:
+ s.until_text("V=[")
+ screen = s.snapshot().screen()
+ check("a pane really is told it is in one", "V=[1]" in screen, screen)
+ check("--script has no socket, so it names no session",
+ "S=[]" in screen, screen)
+ check("...and clears an inherited one rather than passing it on",
+ "somebody-elses-session" not in screen, screen)
+ check("the binary is the one that made the pane, not an inherited path",
+ "/nope/x" not in screen and "sl0ppty" in screen.split("B=[")[1],
+ screen)
flat = " ".join(body.split()) # the file is wrapped; the claim is not
check("which the skill says out loud",
"unset under `--script`" in flat, "not documented")
+def test_a_real_session_names_itself_to_its_panes():
+ """The other half, and the reason `--script` clearing it is not just
+ `unsetenv` everywhere: a session *does* tell its panes which one it is, and it
+ is its own name even when it was started from inside another session."""
+ out = os.path.join(tempfile.mkdtemp(prefix="sl0ppty-env-"), "seen")
+ name = "envclaim-%d" % os.getpid()
+ env = dict(os.environ)
+ env["SL0PPTY_SESSION"] = "somebody-elses-session"
+ try:
+ subprocess.run([BIN, "-s", name, "--", "/bin/sh", "-c",
+ 'echo "S=[$SL0PPTY_SESSION]" > %s; read x' % out],
+ capture_output=True, text=True, timeout=30, input="", env=env)
+ deadline = time.time() + 5
+ seen = ""
+ while time.time() < deadline:
+ if os.path.exists(out):
+ seen = open(out).read().strip()
+ if seen:
+ break
+ time.sleep(0.05)
+ check("a pane in a real session is told which session that is",
+ seen == "S=[%s]" % name, repr(seen))
+ finally:
+ subprocess.run([BIN, "-s", name, "cmd", '{"cmd":"quit"}'],
+ capture_output=True, text=True)
+
+
def test_the_panes_fields_it_tells_agents_to_poll_are_real():
"""The skill's central recipe polls `alive` and `exit_code` off `panes` and
filters on `purpose`. If any of those three moved, the recipe is a hang."""