Files
roboco/tests/unit/services/test_git_lock_cleanup.py
T
Renn F 3441e37120 [sweep] strip Fxxx audit-ID tokens + trim bloated comments/docstrings + add behavior-change docs
Post-audit sweep over the 135 audit-fix commits since 19a474d3:

1. Stripped every # Fxxx: audit-ID token from comments AND every Fxxx token
   from docstring openings across 211 blocks / ~626 lines. The CEO flagged
   these twice: audit-issue IDs in code confuse future devs/agents. The
   descriptive text is preserved; only the Fxxx token is removed (and bloated
   narrative blocks trimmed to 1-3 lines keeping the one non-obvious invariant).
2. Trimmed bloated comments/docstrings to the concise standard (1-3 lines).
3. Added missing behavior-change docs for the audit-fix batch: prompts/roles
   (documenter, pr_reviewer, qa), user-facing docs (api auth, websockets,
   agent-gateway, megatask, merge-model, task-lifecycle, grok, resilience,
   conventions, panel, security, troubleshooting), and the RAG corpus (cell-pm,
   main-pm, pr-reviewer, qa roles; conventions; messaging-tools; escalation;
   megatask; task-claiming workflows).

Comment/docstring/prose ONLY — zero code-line edits (verified: the diff
contains no def/class/return/if/for/await/assignment/call lines). Gates green:
ruff format + ruff check clean, mypy clean on roboco/. The only pytest failures
are the pre-existing sync_branch tracing-decision gap (B1, 250be5c2) — not
sweep-caused and tracked separately.
2026-06-29 01:25:40 +02:00

108 lines
3.8 KiB
Python

"""A git mutation op killed by ``_run_git``'s timeout best-effort removes
orphaned ``.git/**/*.lock`` files before re-raising — the SIGKILL'd git
process can't clean up itself, and the locks wedge every subsequent op.
"""
from __future__ import annotations
import subprocess
from typing import TYPE_CHECKING
from unittest.mock import MagicMock
import pytest
from roboco.exceptions import GitTimeoutError
from roboco.services.git import GitService
if TYPE_CHECKING:
from pathlib import Path
def _svc() -> GitService:
return GitService(MagicMock())
def _seed_locks(workspace: Path) -> dict[str, Path]:
"""Create a realistic set of orphaned git lock files under .git/."""
git_dir = workspace / ".git"
refs_heads = git_dir / "refs" / "heads"
refs_heads.mkdir(parents=True, exist_ok=True)
locks = {
"index": git_dir / "index.lock",
"head": git_dir / "HEAD.lock",
"packed_refs": git_dir / "packed-refs.lock",
"ref": refs_heads / "feature.lock",
}
for p in locks.values():
p.write_text("stale")
# A non-lock file that must be left untouched.
keep = git_dir / "config"
keep.write_text("[core]\n")
return {**locks, "keep_config": keep}
@pytest.mark.asyncio
async def test_timeout_removes_stale_git_locks(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A timed-out git op clears orphaned .git locks before re-raising."""
workspace = tmp_path / "ws"
(workspace / ".git").mkdir(parents=True)
locks = _seed_locks(workspace)
def _boom(*_a: object, **_k: object) -> subprocess.CompletedProcess[str]:
raise subprocess.TimeoutExpired(cmd=["git", "commit"], timeout=5)
monkeypatch.setattr("roboco.services.git.subprocess.run", _boom)
with pytest.raises(GitTimeoutError):
await _svc()._run_git(workspace, ["commit", "-m", "x"], check=True)
# Every orphaned lock file is gone — the workspace is un-wedged.
for name in ("index", "head", "packed_refs", "ref"):
assert not locks[name].exists(), f"{name} lock was not cleaned up"
# Non-lock .git files are untouched.
assert locks["keep_config"].exists()
@pytest.mark.asyncio
async def test_timeout_lock_cleanup_is_best_effort_no_git_dir(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A timeout with no .git/ directory must not error the cleanup path."""
workspace = tmp_path / "ws"
workspace.mkdir()
def _boom(*_a: object, **_k: object) -> subprocess.CompletedProcess[str]:
raise subprocess.TimeoutExpired(cmd=["git", "reset", "--hard"], timeout=5)
monkeypatch.setattr("roboco.services.git.subprocess.run", _boom)
with pytest.raises(GitTimeoutError):
await _svc()._run_git(workspace, ["reset", "--hard", "origin/main"])
# No crash — that's the assertion (best-effort cleanup on a missing .git).
@pytest.mark.asyncio
async def test_clean_exit_does_not_touch_locks(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A normal (non-timed-out) git op must NOT delete lock files — a concurrent
real git process could be holding one. Cleanup is timeout-only."""
workspace = tmp_path / "ws"
(workspace / ".git").mkdir(parents=True)
locks = _seed_locks(workspace)
ok = subprocess.CompletedProcess(
args=["git", "status"], returncode=0, stdout="", stderr=""
)
monkeypatch.setattr("roboco.services.git.subprocess.run", lambda *_a, **_k: ok)
# _ensure_agent_owned runs after a clean exit — no-op it.
monkeypatch.setattr(
"roboco.services.workspace._ensure_agent_owned", lambda _ws: None
)
await _svc()._run_git(workspace, ["status"], check=True)
for name in ("index", "head", "packed_refs", "ref"):
assert locks[name].exists(), f"{name} lock was wrongly removed on clean exit"