"""Coverage batch v4-D6: host.py parallel-branch fallback paths (4186 dep-branch dispatch failure, 4198 dep cwd copy). Stubbing discipline: the branch code under test is never stubbed. Patched collaborators are dispatch, dispatch_from_branch, run_task_turn_with_retry and copy_tree_excluding_git (all of them sinks the branch delegates to). Assertions read real stdout and the real copy_tree call record. """ import contextlib import io import json import os import sys import tempfile import unittest from pathlib import Path from unittest import mock REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT)) import host class _FakeServer: def __init__(self, *args, **kwargs): pass def initialize(self): pass def set_trace_file(self, *args, **kwargs): pass def close(self): pass class ParallelFallbackTestCase(unittest.TestCase): def test_dep_branch_dispatch_falls_back_to_head_plus_copy(self): # 4186 + 4198 with tempfile.TemporaryDirectory() as tmp: dep_dir = os.path.join(tmp, "dep-cwd") os.makedirs(dep_dir) with open(os.path.join(dep_dir, "seed.txt"), "w") as f: f.write("seed") new_dir = os.path.join(tmp, "new-cwd") jobs_path = os.path.join(tmp, "jobs.json") jobs = [ {"name": "j1", "task": "t1", "repo": "/tmp/repo1"}, {"name": "j2", "task": "t2", "repo": "/tmp/repo1", "depends_on": ["j1"]}, ] with open(jobs_path, "w", encoding="utf-8") as f: json.dump(jobs, f) state_path = os.path.join(tmp, "host-state.json") cfg_path = os.path.join(tmp, "host-config.json") with open(state_path, "w", encoding="utf-8") as f: json.dump({"tasks": {}, "automations": []}, f) with open(cfg_path, "w", encoding="utf-8") as f: json.dump({}, f) def _dispatch(srv, state, name, task, repo, *a, **k): if name == "j1": return {"name": "j1", "status": "completed", "branch": "b1", "cwd": dep_dir} return {"name": "j2", "status": "completed", "cwd": new_dir} copy_calls = [] argv = ["host.py", "--state", state_path, "--config", cfg_path, "parallel", "--jobs", jobs_path] buf = io.StringIO() with contextlib.redirect_stdout(buf): with mock.patch.object(sys, "argv", argv): with mock.patch.object( host, "Server", lambda **kw: _FakeServer()): with mock.patch.object( host, "dispatch", side_effect=_dispatch): with mock.patch.object( host, "dispatch_from_branch", return_value=(None, "boom")): with mock.patch.object( host, "run_task_turn_with_retry", return_value={"status": "completed", "text": "ok"}): with mock.patch.object( host, "copy_tree_excluding_git", side_effect=lambda s, d: copy_calls.append((s, d))): host.main() out = buf.getvalue() self.assertIn("从依赖分支派发失败", out) self.assertEqual(copy_calls, [(dep_dir, new_dir)]) if __name__ == "__main__": unittest.main()