agent-exec transport 973ce511e924ce5ab0953e16e46a42ec/7671c49f7667
This commit is contained in:
@@ -469,6 +469,209 @@ class TaskRecoveryTests(unittest.TestCase):
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "refs/remotes/origin/main").stdout,
|
||||
cached_before)
|
||||
|
||||
def write_no_cache_wrapper(self, bin_dir, advertised, log_name="git.log"):
|
||||
"""Create a disposable git wrapper for the missing-tracking-ref scenarios.
|
||||
|
||||
Intercepts:
|
||||
fetch -> fail (origin unreachable; the wrapper itself exits 128
|
||||
and writes the standard "unable to access origin" stderr line
|
||||
so the precheck captures the same `fetch_err` it would observe
|
||||
against a real unreachable remote)
|
||||
ls-remote origin refs/heads/main -> configured ADVERTISED (or
|
||||
nothing when ADVERTISED is empty, simulating an origin that
|
||||
has no configured target branch)
|
||||
branch --merged origin/main -> empty output (the cached
|
||||
tracking ref is deleted locally; the real git would otherwise
|
||||
fail the pipeline under set -e/pipefail and the precheck would
|
||||
silently exit before the verdict is printed)
|
||||
|
||||
All other git calls delegate to the real binary via the resolved
|
||||
REAL_GIT absolute path (and a stripped PATH so the wrapper cannot
|
||||
recurse into itself) so the registered task context stays
|
||||
observable normally.
|
||||
|
||||
Returns (bin_dir, wrapper_log_path)."""
|
||||
f = self.fixture
|
||||
real_git = subprocess.check_output(
|
||||
["bash", "-c", "command -v git"], env=f.env, text=True).strip()
|
||||
bin_dir.mkdir(exist_ok=True)
|
||||
wrapper_log = bin_dir / log_name
|
||||
wrapper = bin_dir / "git"
|
||||
wrapper.write_text(
|
||||
"#!/usr/bin/env bash\n"
|
||||
"# Disposable fixture wrapper for the missing-tracking-ref\n"
|
||||
"# scenarios:\n"
|
||||
"# fetch -> fail (128)\n"
|
||||
"# ls-remote origin refs/heads/main -> $ADVERTISED\n"
|
||||
"# branch --merged origin/main -> empty\n"
|
||||
"# Anything else delegates to the real binary so the registered\n"
|
||||
"# task context (HEAD, branch, R0, worktree, .task/) stays\n"
|
||||
"# observable normally.\n"
|
||||
f"REAL_GIT={shlex.quote(real_git)}\n"
|
||||
f"LOG={shlex.quote(str(wrapper_log))}\n"
|
||||
f"ADVERTISED={shlex.quote(advertised)}\n"
|
||||
"WRAPPER_BIN=$(cd \"$(dirname \"$0\")\" && pwd)\n"
|
||||
"echo \"configured advertised-target=$ADVERTISED\" >> \"$LOG\"\n"
|
||||
"stripped=\"\"\n"
|
||||
"old_ifs=\"$IFS\"\n"
|
||||
"IFS=':'\n"
|
||||
"for p in $PATH; do\n"
|
||||
" [ \"$p\" = \"$WRAPPER_BIN\" ] && continue\n"
|
||||
" if [ -z \"$stripped\" ]; then stripped=\"$p\"; else stripped=\"$stripped:$p\"; fi\n"
|
||||
"done\n"
|
||||
"IFS=\"$old_ifs\"\n"
|
||||
"export PATH=\"$stripped\"\n"
|
||||
"case \"$1\" in\n"
|
||||
" fetch)\n"
|
||||
" echo \"fetch-intercepted\" >> \"$LOG\"\n"
|
||||
" echo 'fatal: unable to access origin' >&2\n"
|
||||
" exit 128 ;;\n"
|
||||
" ls-remote)\n"
|
||||
" if [ \"${2:-}\" = origin ] && [ \"${3:-}\" = refs/heads/main ]; then\n"
|
||||
" echo \"ls-remote-served advertised-target=$ADVERTISED\" >> \"$LOG\"\n"
|
||||
" if [ -n \"$ADVERTISED\" ]; then\n"
|
||||
" printf '%s\\trefs/heads/main\\n' \"$ADVERTISED\"\n"
|
||||
" fi\n"
|
||||
" exit 0\n"
|
||||
" fi\n"
|
||||
" exec \"$REAL_GIT\" \"$@\" ;;\n"
|
||||
" branch)\n"
|
||||
" # refs/remotes/origin/main is deleted locally; the real git\n"
|
||||
" # `git branch --merged origin/main` would fail and the\n"
|
||||
" # precheck pipeline (with pipefail + set -e) would silently\n"
|
||||
" # exit. Stub it so the verdict is actually emitted.\n"
|
||||
" if [ \"${2:-}\" = --merged ] && [ \"${3:-}\" = origin/main ]; then\n"
|
||||
" echo \"branch-merged-stub no-merged-branches\" >> \"$LOG\"\n"
|
||||
" exit 0\n"
|
||||
" fi\n"
|
||||
" exec \"$REAL_GIT\" \"$@\" ;;\n"
|
||||
"esac\n"
|
||||
"exec \"$REAL_GIT\" \"$@\"\n")
|
||||
wrapper.chmod(0o755)
|
||||
return bin_dir, wrapper_log
|
||||
|
||||
def test_local_precheck_without_cached_ref_rejects_uncomparable_remote(self):
|
||||
"""Cached refs/remotes/origin/main is absent; origin advertises a
|
||||
SHA whose object is unavailable locally. Local precheck must not
|
||||
advertise LOCAL_READY because ancestry cannot be compared against
|
||||
any locally known target — the cached target cannot be trusted
|
||||
as authoritative without an ancestry check, and the advertised
|
||||
SHA is not locally reachable so no forward-descendant
|
||||
relationship can be established either.
|
||||
|
||||
The current implementation lets target_history() short-circuit at
|
||||
the missing cached-ref step (return "ok") and unconditionally
|
||||
print LOCAL_READY in local mode. The verdict is wrong; this test
|
||||
records the expected red result."""
|
||||
f = self.fixture
|
||||
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
# Detach the registered branch from its (now stale) upstream so
|
||||
# no in-process code accidentally queries HEAD@{u} / remote.merge
|
||||
# against the deleted cached ref.
|
||||
f.git(self.wt, "branch", "--unset-upstream", "feature/test")
|
||||
f.git(self.wt, "update-ref", "-d", "refs/remotes/origin/main")
|
||||
|
||||
synthetic = "1" * 40
|
||||
bin_dir, wrapper_log = self.write_no_cache_wrapper(
|
||||
f.root / "no-cache-uncomparable-bin", synthetic)
|
||||
path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}"
|
||||
wrapper_env = f.env | {"PATH": path_env}
|
||||
|
||||
local = f.task(self.wt, "precheck", check=False,
|
||||
env=wrapper_env | {"TASK_WORK_MODE": "local"})
|
||||
|
||||
# Proof: the wrapper was loaded (configured marker) and served
|
||||
# the failure case (fetch intercepted). ls-remote is served by
|
||||
# the wrapper if and when the post-fix implementation queries it
|
||||
# for the missing cached-ref path; the current implementation
|
||||
# short-circuits before ls-remote, so the assertion below does
|
||||
# not require an ls-remote entry.
|
||||
log = wrapper_log.read_text()
|
||||
self.assertIn(f"configured advertised-target={synthetic}", log)
|
||||
self.assertIn("fetch-intercepted", log)
|
||||
|
||||
self.assertNotEqual(local.returncode, 0, local.stdout)
|
||||
self.assertNotIn("[verdict] LOCAL_READY", local.stdout)
|
||||
self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
|
||||
def test_local_precheck_without_cached_ref_rejects_missing_remote_target(self):
|
||||
"""Cached refs/remotes/origin/main is absent; origin returns no
|
||||
configured target branch (ls-remote outputs nothing). Local
|
||||
precheck must not advertise LOCAL_READY because nothing can be
|
||||
verified against the registered task's R0 — the cached target
|
||||
is the only handle on what origin claimed, and without it (and
|
||||
without an ls-remote response) the precheck has no
|
||||
authoritative signal to confirm the target even exists.
|
||||
|
||||
The current implementation lets target_history() short-circuit at
|
||||
the missing cached-ref step (return "ok") and unconditionally
|
||||
print LOCAL_READY in local mode. The verdict is wrong; this test
|
||||
records the expected red result."""
|
||||
f = self.fixture
|
||||
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
f.git(self.wt, "branch", "--unset-upstream", "feature/test")
|
||||
f.git(self.wt, "update-ref", "-d", "refs/remotes/origin/main")
|
||||
|
||||
bin_dir, wrapper_log = self.write_no_cache_wrapper(
|
||||
f.root / "no-cache-missing-target-bin", "")
|
||||
path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}"
|
||||
wrapper_env = f.env | {"PATH": path_env}
|
||||
|
||||
local = f.task(self.wt, "precheck", check=False,
|
||||
env=wrapper_env | {"TASK_WORK_MODE": "local"})
|
||||
|
||||
log = wrapper_log.read_text()
|
||||
# Empty advertised value means the wrapper emits no ls-remote line.
|
||||
self.assertIn("configured advertised-target=", log)
|
||||
self.assertIn("fetch-intercepted", log)
|
||||
|
||||
self.assertNotEqual(local.returncode, 0, local.stdout)
|
||||
self.assertNotIn("[verdict] LOCAL_READY", local.stdout)
|
||||
self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
|
||||
def test_local_precheck_without_cached_ref_allows_comparable_forward_target(self):
|
||||
"""Cached refs/remotes/origin/main is absent; origin advertises
|
||||
the task's R0 (or a forward descendant that is locally
|
||||
available). Local precheck must continue to advertise
|
||||
LOCAL_READY — the explicit missing-tracking-ref continuation
|
||||
contract — because the registered task context is the same as
|
||||
the cached ref would have represented, and the ls-remote
|
||||
response can be compared against the registered baseline to
|
||||
confirm origin is at or past the task's initial state.
|
||||
|
||||
The current implementation short-circuits at the missing
|
||||
cached-ref step (return "ok"), which coincidentally returns
|
||||
LOCAL_READY for any registered task. The verdict is right; this
|
||||
test records the expected green result so the post-fix
|
||||
implementation preserves the contract when the ls-remote check
|
||||
is added against the registered R0."""
|
||||
f = self.fixture
|
||||
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
f.git(self.wt, "branch", "--unset-upstream", "feature/test")
|
||||
f.git(self.wt, "update-ref", "-d", "refs/remotes/origin/main")
|
||||
|
||||
bin_dir, wrapper_log = self.write_no_cache_wrapper(
|
||||
f.root / "no-cache-comparable-bin", r0)
|
||||
path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}"
|
||||
wrapper_env = f.env | {"PATH": path_env}
|
||||
|
||||
local = f.task(self.wt, "precheck", check=False,
|
||||
env=wrapper_env | {"TASK_WORK_MODE": "local"})
|
||||
|
||||
log = wrapper_log.read_text()
|
||||
self.assertIn(f"configured advertised-target={r0}", log)
|
||||
self.assertIn("fetch-intercepted", log)
|
||||
|
||||
self.assertEqual(local.returncode, 0, local.stdout)
|
||||
self.assertIn("[verdict] LOCAL_READY", local.stdout)
|
||||
self.assertNotIn("[verdict] REMOTE_UNVERIFIED", local.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user