From 3632b21135760e072a9ccb172212441c0c03339b Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Tue, 27 Oct 2026 11:52:43 +0000 Subject: [PATCH] test_git counts only its own ssh-agents (#172) 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=, 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) Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk --- .../src/nisse_core/execution/testing.py | 22 +++++++++++- nisse-dev/tests/test_git.py | 35 ++++++++++++++----- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/nisse-core/src/nisse_core/execution/testing.py b/nisse-core/src/nisse_core/execution/testing.py index e6238f3..ee58165 100644 --- a/nisse-core/src/nisse_core/execution/testing.py +++ b/nisse-core/src/nisse_core/execution/testing.py @@ -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}" + ), "", ] ) diff --git a/nisse-dev/tests/test_git.py b/nisse-dev/tests/test_git.py index 9771b88..4d04469 100644 --- a/nisse-dev/tests/test_git.py +++ b/nisse-dev/tests/test_git.py @@ -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