155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from robomp.sandbox import SandboxManager, make_branch, workspace_key
|
|
|
|
|
|
def _git(args: list[str], cwd: Path) -> None:
|
|
subprocess.run(["git", *args], cwd=str(cwd), check=True, capture_output=True, text=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def upstream_repo(tmp_path: Path) -> Path:
|
|
"""Create a local --bare-ish remote with one commit on main."""
|
|
repo = tmp_path / "upstream.git"
|
|
repo.mkdir()
|
|
_git(["init", "--initial-branch=main", "--bare", str(repo)], cwd=tmp_path)
|
|
seed = tmp_path / "seed"
|
|
seed.mkdir()
|
|
_git(["init", "--initial-branch=main", str(seed)], cwd=tmp_path)
|
|
(seed / "README.md").write_text("hello\n", encoding="utf-8")
|
|
_git(["-C", str(seed), "add", "."], cwd=tmp_path)
|
|
env = os.environ | {
|
|
"GIT_AUTHOR_NAME": "t",
|
|
"GIT_AUTHOR_EMAIL": "t@t",
|
|
"GIT_COMMITTER_NAME": "t",
|
|
"GIT_COMMITTER_EMAIL": "t@t",
|
|
}
|
|
subprocess.run(
|
|
["git", "commit", "-m", "init"],
|
|
cwd=str(seed),
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
env=env,
|
|
)
|
|
_git(["-C", str(seed), "remote", "add", "origin", str(repo)], cwd=tmp_path)
|
|
_git(["-C", str(seed), "push", "origin", "main"], cwd=tmp_path)
|
|
return repo
|
|
|
|
|
|
def test_workspace_key_and_branch_shape() -> None:
|
|
assert workspace_key("oven-sh/bun", 30654) == "oven-sh__bun__30654"
|
|
branch = make_branch(issue_number=30654, title="JSON.parse crashes on BOM", seed="oven-sh/bun#30654")
|
|
assert branch.startswith("farm/")
|
|
parts = branch.split("/")
|
|
assert len(parts) == 3 and len(parts[1]) == 8
|
|
assert "json-parse-crashes" in parts[2]
|
|
|
|
|
|
def test_ensure_workspace_creates_worktree(tmp_path: Path, upstream_repo: Path) -> None:
|
|
mgr = SandboxManager(tmp_path / "workspaces")
|
|
ws = mgr.ensure_workspace(
|
|
repo="octo/widget",
|
|
number=42,
|
|
title="something is wrong",
|
|
clone_url=str(upstream_repo),
|
|
default_branch="main",
|
|
author_name="robomp-bot",
|
|
author_email="robomp-bot@example.invalid",
|
|
)
|
|
assert ws.repo_dir.is_dir()
|
|
assert (ws.repo_dir / "README.md").read_text() == "hello\n"
|
|
# Branch is checked out.
|
|
result = subprocess.run(
|
|
["git", "-C", str(ws.repo_dir), "rev-parse", "--abbrev-ref", "HEAD"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
assert result.stdout.strip() == ws.branch
|
|
assert ws.branch.startswith("farm/")
|
|
# Session and context dirs exist.
|
|
assert ws.session_dir.is_dir()
|
|
assert ws.context_dir.is_dir()
|
|
assert ws.repro_dir.is_dir()
|
|
assert ws.artifacts_dir.is_dir()
|
|
|
|
|
|
def test_ensure_workspace_is_idempotent(tmp_path: Path, upstream_repo: Path) -> None:
|
|
mgr = SandboxManager(tmp_path / "workspaces")
|
|
ws1 = mgr.ensure_workspace(
|
|
repo="octo/widget",
|
|
number=5,
|
|
title="t",
|
|
clone_url=str(upstream_repo),
|
|
default_branch="main",
|
|
author_name="robomp-bot",
|
|
author_email="robomp-bot@example.invalid",
|
|
)
|
|
ws2 = mgr.ensure_workspace(
|
|
repo="octo/widget",
|
|
number=5,
|
|
title="t",
|
|
clone_url=str(upstream_repo),
|
|
default_branch="main",
|
|
author_name="robomp-bot",
|
|
author_email="robomp-bot@example.invalid",
|
|
)
|
|
assert ws1.repo_dir == ws2.repo_dir
|
|
assert ws1.branch == ws2.branch
|
|
|
|
|
|
def test_remove_workspace(tmp_path: Path, upstream_repo: Path) -> None:
|
|
mgr = SandboxManager(tmp_path / "workspaces")
|
|
ws = mgr.ensure_workspace(
|
|
repo="octo/widget",
|
|
number=12,
|
|
title="t",
|
|
clone_url=str(upstream_repo),
|
|
default_branch="main",
|
|
author_name="robomp-bot",
|
|
author_email="robomp-bot@example.invalid",
|
|
)
|
|
assert ws.repo_dir.exists()
|
|
mgr.remove_workspace(repo="octo/widget", number=12)
|
|
assert not ws.repo_dir.exists()
|
|
assert not ws.root.exists()
|
|
|
|
|
|
def test_redact_credentials_strips_userinfo() -> None:
|
|
from robomp.sandbox import redact_credentials
|
|
|
|
assert (
|
|
redact_credentials("Cloning into 'x' from https://bot:ghp_secret@github.com/o/r.git failed")
|
|
== "Cloning into 'x' from https://***@github.com/o/r.git failed"
|
|
)
|
|
# Multiple URLs in one string.
|
|
assert (
|
|
redact_credentials("a https://x:y@example.com b https://q:z@example.org c")
|
|
== "a https://***@example.com b https://***@example.org c"
|
|
)
|
|
# No-op on strings without credentials.
|
|
assert redact_credentials("plain message") == "plain message"
|
|
assert redact_credentials(None) == ""
|
|
|
|
|
|
def test_git_command_error_redacts_url_in_args_and_stderr(tmp_path: Path) -> None:
|
|
"""An ENOENT-style git failure on a credentialed clone URL must not echo the token."""
|
|
import pytest as _pytest
|
|
|
|
from robomp.sandbox import _run
|
|
|
|
cred_url = "https://bot:ghp_abc123secret@example.invalid/o/r.git"
|
|
with _pytest.raises(Exception) as exc:
|
|
_run(["git", "clone", cred_url, str(tmp_path / "out")])
|
|
text = str(exc.value)
|
|
assert "ghp_abc123secret" not in text
|
|
assert "bot" not in text or "https://bot:" not in text
|
|
assert "***" in text or "example.invalid" in text
|