Compare commits

1 Commits
Author SHA1 Message Date
dongarandClaude Opus 5.5 3632b21135 test_git counts only its own ssh-agents (#172)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m35s
CI / e2e (pull_request) Successful in 3m46s
The unit tier runs under xdist, so another worker's push could start an
ssh-agent between a test's count and its check. The test sshd now tags
every session with NISSE_TEST_SSHD=<its root>, and the tests count only
agents carrying their own sshd's tag. A new test starts a foreign agent
alongside and checks it isn't counted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-27 11:52:43 +00:00
2 changed files with 48 additions and 9 deletions

No files matched your search

+21 -1
View File
@@ -18,6 +18,21 @@ from nisse_core.execution.ssh import SSHHost
SSHD = "/usr/sbin/sshd"
# Set in every session of a local sshd, to its root directory.
SESSION_MARK = "NISSE_TEST_SSHD"
def started_by(host: SSHHost, pid: str) -> bool:
"""Whether process ``pid`` was started in a session of ``host``'s local
sshd: it carries that sshd's SESSION_MARK. A process that's gone, or
another user's, wasn't."""
mark = f"{SESSION_MARK}={host._key.parent}".encode() # the sshd root
try:
environ = Path(f"/proc/{pid}/environ").read_bytes()
except OSError:
return False
return mark in environ.split(b"\0")
def _free_port() -> int:
with socket.socket() as sock:
@@ -61,7 +76,12 @@ def local_sshd(root: Path) -> Iterator[SSHHost]:
"MaxStartups 50",
# Stands in for a provisioned build VM: sessions see the test
# runner's PATH, so its Python and tools are "installed".
f"SetEnv PATH={os.environ.get('PATH', '/usr/bin:/bin')}",
# SESSION_MARK tags every process this sshd starts, so a test
# can tell its own from another worker's (#172).
(
f"SetEnv PATH={os.environ.get('PATH', '/usr/bin:/bin')} "
f"{SESSION_MARK}={root}"
),
"",
]
)
+27 -8
View File
@@ -7,6 +7,7 @@ from pathlib import Path
import pytest
from nisse_core.execution.ssh import SSHHost
from nisse_core.execution.testing import started_by
from nisse_dev.git import GitError, GitRepo, HTTPSToken, SSHKey
@@ -113,9 +114,9 @@ def test_the_key_is_gone_when_the_command_ends(
repo.configure_identity("t", "t@example.test")
(repo.root / "a.txt").write_text("a")
repo.commit_all("first")
before = _agents()
before = _agents(ssh_host)
repo.push(url, "main", credential=key)
assert _agents() == before # the throwaway ssh-agent was killed
assert _agents(ssh_host) == before # the throwaway ssh-agent was killed
def test_a_failed_push_still_kills_its_agent(
@@ -124,19 +125,35 @@ def test_a_failed_push_still_kills_its_agent(
_, key = ssh_remote
repo = GitRepo(ssh_host, tmp_path / "w", known_hosts=str(tmp_path / "kh"))
repo.init()
before = _agents()
before = _agents(ssh_host)
with pytest.raises(GitError):
repo.push(
f"ssh://nobody@127.0.0.1:{ssh_host.port}/nowhere.git",
"main",
credential=key,
)
assert _agents() == before
assert _agents(ssh_host) == before
def _agents() -> set[str]:
"""Live ssh-agents. A killed one lingers as a zombie in a CI container,
where nothing reaps orphans, so zombies don't count."""
def test_an_agent_started_elsewhere_is_not_counted(ssh_host: SSHHost) -> None:
# The unit tier runs in parallel: another worker's push starts its own
# ssh-agent at any moment (#172). Only this sshd's count.
before = _agents(ssh_host)
out = subprocess.run(
["ssh-agent", "-s"], capture_output=True, text=True, check=True
).stdout
pid = out.split("SSH_AGENT_PID=")[1].split(";")[0]
try:
assert _agents(ssh_host) == before
assert pid in _agents(None)
finally:
subprocess.run(["kill", pid], check=False)
def _agents(host: SSHHost | None) -> set[str]:
"""Live ssh-agents started through ``host``'s sshd, or any when None. A
killed one lingers as a zombie in a CI container, where nothing reaps
orphans, so zombies don't count."""
out = subprocess.run(
["pgrep", "-x", "ssh-agent"], capture_output=True, text=True, check=False
).stdout
@@ -146,7 +163,9 @@ def _agents() -> set[str]:
stat = Path(f"/proc/{pid}/stat").read_text()
except FileNotFoundError:
continue
if stat.rsplit(")", 1)[1].split()[0] != "Z":
if stat.rsplit(")", 1)[1].split()[0] == "Z":
continue
if host is None or started_by(host, pid):
live.add(pid)
return live