"""派发队列与沙箱执行器的单测。 运行(在 ~/workspace/codex-app-server 下): python3 -m unittest discover -s cloud/tests -p "test_queue_sandbox.py" -v 说明:用 cloud.queue / cloud.sandbox 包导入,绝不把 cloud/ 目录 插到 sys.path 前面——后者会让顶层模块名 queue 遮蔽标准库 queue, 一旦有其它测试模块先 import 标准库 queue(已缓进 sys.modules), 本文件就会 ImportError(见 TestImportRobustness 的回归证明)。 """ import json import os import subprocess import sys import unittest from pathlib import Path from types import SimpleNamespace from unittest import mock try: from cloud.queue import TaskQueue, budget_exceeded, order_tasks, ready_tasks from cloud.sandbox import (CloudExecutor, DockerExecutor, Executor, LocalExecutor, _parse_goal_output, _parse_host_result, _result_text) except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.queue import TaskQueue, budget_exceeded, order_tasks, ready_tasks from cloud.sandbox import (CloudExecutor, DockerExecutor, Executor, LocalExecutor, _parse_goal_output, _parse_host_result, _result_text) def _task(task_id, depends_on=None, priority=0, status="queued"): task = {"id": task_id, "priority": priority, "status": status} if depends_on is not None: task["depends_on"] = depends_on return task class TestOrderTasks(unittest.TestCase): def test_dependency_order(self): # A -> B 时 B 在 A 后 ordered = order_tasks([_task("B", depends_on=["A"]), _task("A")]) self.assertEqual([t["id"] for t in ordered], ["A", "B"]) def test_dependency_chain(self): ordered = order_tasks([_task("C", depends_on=["B"]), _task("B", depends_on=["A"]), _task("A")]) self.assertEqual([t["id"] for t in ordered], ["A", "B", "C"]) def test_priority_within_wave(self): # 同一波(均无依赖)按 priority 大的先 ordered = order_tasks([_task("low", priority=1), _task("high", priority=10), _task("mid", priority=5)]) self.assertEqual([t["id"] for t in ordered], ["high", "mid", "low"]) def test_priority_does_not_jump_dependency(self): # 高优先级也不能越过依赖:B 依赖 A,即使 B 优先级更高也排 A 后 ordered = order_tasks([_task("B", depends_on=["A"], priority=99), _task("A", priority=0)]) self.assertEqual([t["id"] for t in ordered], ["A", "B"]) def test_missing_dependency_raises(self): with self.assertRaises(ValueError): order_tasks([_task("B", depends_on=["NOPE"])]) def test_cycle_raises(self): with self.assertRaises(ValueError): order_tasks([_task("A", depends_on=["B"]), _task("B", depends_on=["A"])]) def test_self_dependency_raises(self): with self.assertRaises(ValueError): order_tasks([_task("A", depends_on=["A"])]) def test_defaults(self): # depends_on / priority 缺省时按 0 处理、不报错 ordered = order_tasks([{"id": "A"}, {"id": "B"}]) self.assertEqual([t["id"] for t in ordered], ["A", "B"]) class TestReadyTasks(unittest.TestCase): def test_only_dependency_satisfied_queued(self): tasks = [_task("A"), _task("B", depends_on=["A"]), _task("C", depends_on=["A"], status="running"), _task("D", depends_on=["MISSING"])] ready = ready_tasks(tasks, completed_ids=[]) self.assertEqual([t["id"] for t in ready], ["A"]) # A 已完成(状态随之改为 completed)后,B 依赖满足且 queued; # C 是 running 不返回;D 依赖未满足不返回 done_tasks = [_task("A", status="completed"), _task("B", depends_on=["A"]), _task("C", depends_on=["A"], status="running"), _task("D", depends_on=["MISSING"])] ready = ready_tasks(done_tasks, completed_ids=["A"]) self.assertEqual([t["id"] for t in ready], ["B"]) def test_ready_ordered_by_priority(self): tasks = [_task("low", priority=1), _task("high", priority=9)] ready = ready_tasks(tasks, completed_ids=[]) self.assertEqual([t["id"] for t in ready], ["high", "low"]) class TestBudgetExceeded(unittest.TestCase): def test_below_limits(self): self.assertFalse(budget_exceeded(5, 10, 0.5, 1.0)) def test_at_limit_counts_as_exceeded(self): self.assertTrue(budget_exceeded(10, 10, 0.5, 1.0)) self.assertTrue(budget_exceeded(5, 10, 1.0, 1.0)) def test_none_limit_means_unlimited(self): self.assertFalse(budget_exceeded(10 ** 9, None, 10 ** 9, None)) self.assertTrue(budget_exceeded(10 ** 9, None, 10 ** 9, 1.0)) class TestTaskQueue(unittest.TestCase): def test_next_ready_flow(self): queue = TaskQueue() queue.add(_task("A")) queue.add(_task("B", depends_on=["A"])) self.assertEqual(queue.next_ready([])["id"], "A") queue.mark_running("A") # A 在 running,不再是可派任务;B 依赖未完成,也不可派 self.assertIsNone(queue.next_ready([])) queue.mark_done("A", "completed") self.assertEqual(queue.next_ready(["A"])["id"], "B") def test_mark_done_validates_status(self): queue = TaskQueue([_task("A")]) with self.assertRaises(ValueError): queue.mark_done("A", "bogus-status") with self.assertRaises(ValueError): queue.add(_task("A")) # 重复 id def test_all_host_statuses_accepted(self): queue = TaskQueue() for status in ("running", "completed", "failed", "interrupted", "merged", "merge-failed"): queue.add(_task("t-" + status, status=status)) # 非 queued 任务一律不可派 self.assertIsNone(queue.next_ready([])) class TestExecutors(unittest.TestCase): def test_base_executor_must_implement(self): with self.assertRaises(NotImplementedError): Executor().execute({"id": "x"}) def test_local_executor_success(self): seen = [] def fake_runner(cmd): seen.append(cmd) return SimpleNamespace(returncode=0, stdout="ok-status") executor = LocalExecutor("/fake/host.py", config_path="/fake/cfg.json", runner=fake_runner) result = executor.execute({"id": "t1", "project_id": "p", "repo": "/r", "prompt": "hi"}) self.assertEqual(result["executor"], "local") self.assertEqual(result["task_id"], "t1") self.assertEqual(result["status"], "completed") self.assertEqual(result["stdout"], "ok-status") # 只发只读 status 命令,不发 dispatch/run(不直接跑模型) self.assertIn("status", seen[0]) self.assertNotIn("dispatch", seen[0]) self.assertNotIn("run", seen[0]) self.assertIn("--config", seen[0]) def test_local_executor_failure(self): def fake_runner(cmd): return SimpleNamespace(returncode=1, stdout="boom") result = LocalExecutor("/fake/host.py", runner=fake_runner).execute({"id": "t2"}) self.assertEqual(result["status"], "failed") self.assertEqual(result["stdout"], "boom") def test_local_executor_truncates_stdout(self): # 20000 字上限:5000 字不再截断,25000 字截断并带标记。 def fake_runner(cmd): return SimpleNamespace(returncode=0, stdout="x" * 5000) result = LocalExecutor("/fake/host.py", runner=fake_runner).execute({"id": "t3"}) self.assertEqual(len(result["stdout"]), 5000) def long_runner(cmd): return SimpleNamespace(returncode=0, stdout="y" * 25000) result = LocalExecutor("/fake/host.py", runner=long_runner).execute({"id": "t4"}) self.assertIn("结果已截断", result["stdout"]) self.assertTrue(result["stdout"].startswith("y" * 20000)) def test_result_text_extracts_long_json_text(self): # 2026-10-30 生产审计任务的复发:host.py JSON 的 text # 超过旧 2000 字截断时,存库的不能是截断的 JSON, # 必须是完整提取出的 text(本例 5000 字,全部保留)。 text = "z" * 5000 payload = json.dumps({"status": "completed", "text": text}) result = _parse_host_result( "t5", SimpleNamespace(returncode=0, stdout=payload, stderr="")) self.assertEqual(result["stdout"], text) self.assertEqual(result["status"], "completed") def test_result_text_marks_over_limit(self): text = "w" * 21000 self.assertIn("结果已截断", _result_text( {"text": text}, "unused")) def test_docker_executor_no_docker_raises_not_local(self): with mock.patch("cloud.sandbox.shutil.which", return_value=None): with self.assertRaises(RuntimeError) as ctx: DockerExecutor().execute({"id": "t1"}) self.assertIn("docker not available", str(ctx.exception)) # 报错而不是返回 local 结果:绝不静默降级为本地执行 def test_docker_executor_with_docker_runs_container(self): seen = [] def fake_runner(cmd, timeout=None): seen.append(cmd) return SimpleNamespace(returncode=0, stdout="ok-status") with mock.patch("cloud.sandbox.shutil.which", return_value="/usr/bin/docker"): executor = DockerExecutor( "/fake/host.py", runner=fake_runner, env={"XCM_API_KEY": "placeholder-not-a-real-key"}) result = executor.execute({"id": "t1", "worktree": "/tmp/wt1"}) self.assertEqual(result["executor"], "docker") self.assertEqual(result["status"], "completed") self.assertEqual(seen[0][0:2], ["docker", "run"]) self.assertIn("--rm", seen[0]) self.assertIn("/tmp/wt1:/work", seen[0]) self.assertIn("/fake:/app:ro", seen[0]) # -e 只传名,值绝不出现在命令列表里。 self.assertIn("XCM_API_KEY", seen[0]) self.assertNotIn("placeholder-not-a-real-key", seen[0]) self.assertNotIn("placeholder-not-a-real-key", " ".join(seen[0])) def test_cloud_executor_raises_with_cloud_account(self): with self.assertRaises(RuntimeError) as ctx: CloudExecutor().execute({"id": "t1"}) self.assertIn("cloud account", str(ctx.exception)) class TestGoalExecutors(unittest.TestCase): def test_parse_goal_output(self): met, err = _parse_goal_output( "round 1: turn=completed verify_rc=0 verify_out=ok\n" "goal met after rounds\n") self.assertTrue(met) self.assertIsNone(err) met, err = _parse_goal_output( "round 1: turn=completed verify_rc=1 verify_out=file missing\n" "round 2: turn=completed verify_rc=1 verify_out=file missing\n" "goal unmet after rounds\n") self.assertFalse(met) self.assertIn("file missing", err) self.assertIn("2 rounds", err) met, err = _parse_goal_output("some other output\n") self.assertIsNone(met) met, err = _parse_goal_output("") self.assertIsNone(met) def test_local_goal_task_met(self): seen = [] def fake(cmd): seen.append(cmd) return SimpleNamespace( returncode=0, stdout="round 1: turn=completed verify_rc=0 verify_out=ok\n" "goal met after rounds\n", stderr="") executor = LocalExecutor("/fake/host.py", runner=fake) result = executor.goal_task( {"id": "t1", "prompt": "do it", "verify_command": "test -f x", "goal_max_rounds": 2, "repo": "/r"}) self.assertEqual(result["status"], "completed") self.assertTrue(result["goal_met"]) self.assertIsNone(result["goal_error"]) self.assertEqual(result["host_status"], "goal-met") self.assertIn("goal", seen[0]) self.assertIn("--verify", seen[0]) self.assertIn("test -f x", seen[0]) self.assertIn("/r", seen[0]) self.assertEqual(seen[0][seen[0].index("--max-rounds") + 1], "2") def test_local_goal_task_unmet(self): def fake(cmd): return SimpleNamespace( returncode=0, stdout="round 1: turn=completed verify_rc=1 " "verify_out=file missing\n" "goal unmet after rounds\n", stderr="") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "test -f x"}) self.assertEqual(result["status"], "failed") self.assertFalse(result["goal_met"]) self.assertIn("file missing", result["goal_error"]) self.assertEqual(result["host_status"], "goal-unmet") def test_local_goal_task_defaults_prompt_and_rounds(self): seen = [] def fake(cmd): seen.append(list(cmd)) return SimpleNamespace( returncode=0, stdout="goal met after rounds\n", stderr="") LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "true"}) self.assertIn("do it", seen[0]) self.assertEqual(seen[0][seen[0].index("--max-rounds") + 1], "3") def test_local_goal_task_no_conclusion_is_failed_not_completed(self): def fake(cmd): return SimpleNamespace(returncode=0, stdout="nothing\n", stderr="") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "true"}) self.assertEqual(result["status"], "failed") self.assertIsNone(result["goal_met"]) self.assertIn("无结论", result["goal_error"]) def test_local_goal_task_nonzero_without_conclusion_is_interrupted(self): # 2026-10-28 长 goal 实弹:无结论的异常退出(进程被杀、 # 连接断后恢复时抛错)必须记 interrupted 保住断点续跑, # 绝不能一律 failed(旧语义直接断了 60 分钟工作的活路)。 def fake(cmd): return SimpleNamespace(returncode=1, stdout="", stderr="boom") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "true"}) self.assertEqual(result["status"], "interrupted") self.assertIsNone(result["goal_met"]) self.assertIn("boom", result["goal_error"]) def test_local_goal_task_nonzero_with_unmet_stays_failed(self): def fake(cmd): return SimpleNamespace( returncode=1, stdout="round 1: turn=completed verify_rc=1 " "verify_out=file missing\n" "goal unmet after rounds\n", stderr="") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "true"}) self.assertEqual(result["status"], "failed") self.assertIs(result["goal_met"], False) def test_local_goal_task_interrupted_marker(self): def fake(cmd): return SimpleNamespace( returncode=0, stdout="round 1: turn=interrupted verify_rc=- " "verify_out=\n" "goal interrupted after rounds: relay boom\n", stderr="") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "true"}) self.assertEqual(result["status"], "interrupted") self.assertIn("relay boom", result["goal_error"]) def test_docker_goal_task_nonzero_without_conclusion_is_interrupted(self): def fake_runner(cmd, timeout=None): return SimpleNamespace(returncode=1, stdout="", stderr="boom") with mock.patch("cloud.sandbox.shutil.which", return_value="/usr/bin/docker"): executor = DockerExecutor( "/fake/host.py", runner=fake_runner, env={"XCM_API_KEY": "placeholder-not-a-real-key"}) result = executor.goal_task( {"id": "t1", "prompt": "do it", "verify_command": "test -f x"}) self.assertEqual(result["status"], "interrupted") self.assertIn("boom", result["goal_error"]) def test_docker_goal_resume_nonzero_without_conclusion_is_interrupted(self): # 覆盖 docker 侧两处 resume 解析(goal_resume_task 与 # goal_resume_breakpoint_task 共用 shape B 链路,两个都测)。 def fake_runner(cmd, timeout=None): return SimpleNamespace(returncode=1, stdout="", stderr="boom") with mock.patch("cloud.sandbox.shutil.which", return_value="/usr/bin/docker"): executor = DockerExecutor( "/fake/host.py", runner=fake_runner, env={"XCM_API_KEY": "placeholder-not-a-real-key"}) for method in ("goal_resume_task", "goal_resume_breakpoint_task"): result = getattr(executor, method)( {"id": "t1", "prompt": "do it"}) self.assertEqual(result["status"], "interrupted") self.assertIn("boom", result["goal_error"]) def test_goal_resume_refusal_is_failed_not_interrupted(self): # 断点丢失拒绝(2026-10-29 实弹):host-state 里没有任务时 # goal-resume 只打印拒绝行、正常退出;三处解析(本地、 # Docker goal_resume_task、Docker 断点)都必须判 failed # 终态,绝不能判 interrupted——否则自动恢复 12 秒一轮 # 空转到 50 次上限。 refusal = ("goal-resume 拒绝:host-state 里没有该任务 t1," "断点已丢失,不能续跑\n") fake = SimpleNamespace(returncode=0, stdout=refusal, stderr="") result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._goal_result_from_runner("t1", fake) self.assertEqual(result["status"], "failed") self.assertIn("goal-resume 拒绝", result["goal_error"]) def fake_runner(cmd, timeout=None, env=None): return SimpleNamespace(returncode=0, stdout=refusal, stderr="") with mock.patch("cloud.sandbox.shutil.which", return_value="/usr/bin/docker"): executor = DockerExecutor( "/fake/host.py", runner=fake_runner, env={"XCM_API_KEY": "placeholder-not-a-real-key"}) for method in ("goal_resume_task", "goal_resume_breakpoint_task"): result = getattr(executor, method)( {"id": "t1", "prompt": "do it"}) self.assertEqual(result["status"], "failed") self.assertIn("goal-resume 拒绝", result["goal_error"]) def test_local_goal_task_already_exists_names_retry(self): def fake(cmd): return SimpleNamespace( returncode=1, stdout="", stderr="Traceback\nRuntimeError: 任务已存在") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it", "verify_command": "true"}) self.assertEqual(result["status"], "failed") # P1 新语义:goal 失败请用继续而非重试(旧“请新建任务”已废)。 self.assertIn("请用继续而非重试", result["goal_error"]) def test_local_goal_task_without_verify_is_llm_decided(self): # 新语义:没有验收命令不再抛错——走模型自决分支: # 命令里没有 --verify,轮次缺省仍为 3(任务行没写时)。 seen = [] def fake(cmd): seen.append(list(cmd)) return SimpleNamespace( returncode=0, stdout="goal met after rounds\n", stderr="") result = LocalExecutor("/fake/host.py", runner=fake).goal_task( {"id": "t1", "prompt": "do it"}) self.assertEqual(result["status"], "completed") self.assertNotIn("--verify", seen[0]) self.assertEqual(seen[0][seen[0].index("--max-rounds") + 1], "3") def test_base_goal_task_not_implemented(self): with self.assertRaises(NotImplementedError): Executor().goal_task({"id": "t1"}) def test_popen_goal_command(self): seen = [] class _FakePopen: def __init__(self, cmd, **kwargs): seen.append(list(cmd)) with mock.patch("cloud.sandbox.subprocess.Popen", _FakePopen): LocalExecutor("/fake/host.py").popen_task( {"id": "t1", "prompt": "do it", "verify_command": "test -f x", "goal_max_rounds": 2}, kind="goal") self.assertIn("goal", seen[0]) self.assertIn("test -f x", seen[0]) self.assertEqual(seen[0][seen[0].index("--max-rounds") + 1], "2") def test_docker_goal_task_unmet(self): seen = [] def fake_runner(cmd, timeout=None): seen.append(cmd) return SimpleNamespace( returncode=0, stdout="round 1: turn=completed verify_rc=1 " "verify_out=file missing\n" "goal unmet after rounds\n", stderr="") with mock.patch("cloud.sandbox.shutil.which", return_value="/usr/bin/docker"): executor = DockerExecutor( "/fake/host.py", runner=fake_runner, env={"XCM_API_KEY": "placeholder-not-a-real-key"}) result = executor.goal_task( {"id": "t1", "prompt": "do it", "verify_command": "test -f x"}) self.assertEqual(result["executor"], "docker") self.assertEqual(result["status"], "failed") self.assertFalse(result["goal_met"]) self.assertIn("goal", seen[0]) self.assertIn("test -f x", seen[0]) class TestUsageParsing(unittest.TestCase): def test_result_from_runner_usage(self): usage = {"input_tokens": 1000, "cached_input_tokens": 400, "cache_write_input_tokens": 0, "output_tokens": 100, "reasoning_output_tokens": 20, "total_tokens": 1100, "completions": 2} fake = SimpleNamespace( returncode=0, stdout=json.dumps({"status": "completed", "text": "done", "usage": usage}), stderr="") result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._result_from_runner("t1", fake) self.assertEqual(result["usage"], usage) self.assertEqual(result["status"], "completed") def test_result_from_runner_keeps_stdout_full(self): # #165 后 stdout 是解析出的 text 截到 20000 字(不再是 # 2000 字截 JSON),stdout_full 仍必须带完整文本, # 否则超 20000 字的长答案尾部在库里永远查不到。 fake = SimpleNamespace( returncode=0, stdout=json.dumps({"status": "completed", "text": "y" * 5000}), stderr="") result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._result_from_runner("t1", fake) self.assertLessEqual(len(result["stdout"]), 21000) self.assertIn("y" * 5000, result["stdout_full"]) def test_result_from_runner_usage_survives_long_text(self): # usage 在 JSON 末尾:旧的截头解析在 text 很长时会丢 usage、 # 还会把 failed 误判成 completed;解析必须用完整 stdout。 usage = {"input_tokens": 100, "cached_input_tokens": 0, "cache_write_input_tokens": 0, "output_tokens": 10, "reasoning_output_tokens": 0, "total_tokens": 110, "completions": 1} fake = SimpleNamespace( returncode=0, stdout=json.dumps({"status": "failed", "text": "x" * 5000, "usage": usage}), stderr="") result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._result_from_runner("t1", fake) self.assertEqual(result["status"], "failed") self.assertEqual(result["usage"], usage) def test_result_from_runner_dirty_usage_dropped(self): for bad in ({"input_tokens": "abc"}, {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0}, "not-a-dict"): fake = SimpleNamespace( returncode=0, stdout=json.dumps({"status": "completed", "usage": bad}), stderr="") result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._result_from_runner("t1", fake) self.assertIsNone(result["usage"]) def test_goal_usage_line(self): fake = SimpleNamespace( returncode=0, stdout="round 1: turn=completed verify_rc=0 verify_out=ok\n" "goal met after rounds\n" "usage input=100 cached=40 cache_write=0 output=50 " "reasoning=10 total=150 completions=2\n", stderr="") result = LocalExecutor("/fake/host.py", runner=lambda cmd: fake )._goal_result_from_runner("t1", fake) self.assertEqual(result["usage"], { "input_tokens": 100, "cached_input_tokens": 40, "cache_write_input_tokens": 0, "output_tokens": 50, "reasoning_output_tokens": 10, "total_tokens": 150, "completions": 2}) fake2 = SimpleNamespace( returncode=0, stdout="goal met after rounds\n", stderr="") result2 = LocalExecutor("/fake/host.py", runner=lambda cmd: fake2 )._goal_result_from_runner("t1", fake2) self.assertIsNone(result2["usage"]) def test_docker_goal_usage_line(self): def fake_runner(cmd, timeout=None): return SimpleNamespace( returncode=0, stdout="round 1: turn=completed verify_rc=0 verify_out=ok\n" "goal met after rounds\n" "usage input=100 cached=40 cache_write=0 output=50 " "reasoning=10 total=150 completions=2\n", stderr="") with mock.patch("cloud.sandbox.shutil.which", return_value="/usr/bin/docker"): executor = DockerExecutor( "/fake/host.py", runner=fake_runner, env={"XCM_API_KEY": "placeholder-not-a-real-key"}) result = executor.goal_task( {"id": "t1", "prompt": "do it", "verify_command": "test -f x"}) self.assertEqual(result["usage"]["total_tokens"], 150) self.assertEqual(result["usage"]["cached_input_tokens"], 40) class TestImportRobustness(unittest.TestCase): """导入顺序回归证明:本文件绝不依赖顶层 queue 遮蔽标准库。 旧写法把 cloud/ 插到 sys.path 前面再 `from queue import ...`: 一旦其它测试模块先 import 标准库 queue,本文件就 ImportError。 新写法走 cloud.queue / cloud.sandbox 包导入,与标准库 queue 共存,下面的证明在两种层面都钉住这一点。 """ def test_stdlib_queue_not_shadowed(self): # 本文件用的 TaskQueue 必须是 cloud.queue 包导入的,与顶层 # sys.modules["queue"] 无关:全量套件里已有其它测试把 cloud/ # 插进 sys.path 并以顶层名 import queue,sys.modules["queue"] # 在该语境下就是 cloud/queue.py,那是其它测试的污染,不是 # 本文件的依赖——本断言绝不能读 sys.modules["queue"]。 # 标准库 queue 改从 stdlib 目录按路径独立加载、与包导入对照。 import importlib.util import sysconfig self.assertEqual(TaskQueue.__module__, "cloud.queue") stdlib_path = sysconfig.get_paths()["stdlib"] + "/queue.py" spec = importlib.util.spec_from_file_location( "stdlib_queue_for_robustness_check", stdlib_path) stdlib_queue = importlib.util.module_from_spec(spec) spec.loader.exec_module(stdlib_queue) self.assertNotIn("cloud", stdlib_queue.__file__) self.assertNotIn("TaskQueue", dir(stdlib_queue)) # 包导入的 TaskQueue 与标准库无关。 from cloud.queue import TaskQueue as pkg_queue self.assertIs(pkg_queue, TaskQueue) def test_stdlib_queue_preimported_then_module_still_passes(self): # 子进程里先 import 标准库 queue(缓进 sys.modules),再从 # 文件加载本模块并跑全量:旧写法在此处 ImportError,新写法全过。 # 子进程通过环境变量跳过本用例自身,避免无限递归派生。 if os.environ.get("_QUEUE_SANDBOX_ROBUSTNESS_CHILD") == "1": self.skipTest("子进程内跳过,避免递归派生") test_file = str(Path(__file__).resolve()) repo_root = str(Path(__file__).resolve().parents[2]) child_code = ( "import queue, sys\n" "assert 'TaskQueue' not in dir(queue)\n" "import importlib.util, unittest\n" "spec = importlib.util.spec_from_file_location(\n" " 'test_queue_sandbox_stdlib_first', %r)\n" "mod = importlib.util.module_from_spec(spec)\n" "spec.loader.exec_module(mod)\n" "suite = unittest.defaultTestLoader.loadTestsFromModule(mod)\n" "res = unittest.TextTestRunner(verbosity=0).run(suite)\n" "sys.exit(0 if res.wasSuccessful() else 1)\n" % test_file ) env = dict(os.environ) env["_QUEUE_SANDBOX_ROBUSTNESS_CHILD"] = "1" env["PYTHONPATH"] = repo_root proc = subprocess.run( [sys.executable, "-c", child_code], env=env, cwd=repo_root, capture_output=True, text=True, timeout=120) self.assertEqual(proc.returncode, 0, msg=proc.stdout + proc.stderr) self.assertIn("OK", proc.stderr) if __name__ == "__main__": unittest.main()