审批、权限、hooks 与评审调解套件
这一阶段像系统干活时的“门卫和裁判”,不在开机或关机,而是在模型要执行命令、改文件、联网、调工具、问用户时把关。审批和执行策略测试负责确认危险动作会被拦住,批准后也只放开该放的范围;临时权限和技能脚本测试防止权限越滚越大;用户追问、通知、代码评审和 Guardian 检查负责把人与自动审查接好;hook 和 MCP 元数据测试则确保外部工具前后都有记录、拦截和路由,不让安全规则漏掉。
审批策略执行
这些测试确立核心运行时审批和执行策略矩阵,包括通用审批、统一 exec 行为以及技能脚本权限边界。
core/tests/suite/approvals.rs源码 ↗
这个文件像一张安全检查清单,专门测试 Codex 的“权限审批”行为。沙盒可以理解成给程序划出的安全活动范围:有的模式完全放开,有的只能读,有的只能写工作区;审批策略则决定什么时候必须问用户。文件先定义了各种动作,比如写文件、访问网址、运行 shell 命令、应用补丁;再定义期望结果,比如文件应该被创建、网络应该失败、命令应该被拒绝。随后用一大批场景把不同权限组合跑一遍。测试会用假服务器模拟模型返回工具调用,再观察 Codex 是否发出审批请求,模拟用户批准或拒绝,最后检查真实结果。它还覆盖一些容易出错的细节,例如“本次会话批准后同一文件不再重复询问”“批准命令规则后规则会写入磁盘”“子代理里的批准规则能传回父会话”“网络规则批准或拒绝后是否持久保存”。
TargetPath::resolve_for_patch77–90 ↗
fn resolve_for_patch(self, test: &TestCodex) -> (PathBuf, String)
作用:把测试里写的“目标位置”转换成真正的文件路径,同时给补丁工具一份能识别的路径字符串。这样同一套测试既能写工作区内文件,也能故意写到工作区外,检查安全边界。
数据流:进去的是一个目标路径标记和当前测试环境 → 如果目标在工作区,就拼到测试工作目录下;如果在工作区外,就拼到当前进程目录下 → 出来是真实路径和补丁里要使用的路径文本。
调用关系:准备写文件或打补丁时会用到它,主要被 ActionKind::prepare 和 Expectation::verify 间接依赖。它只负责把“我要写哪里”说清楚,不负责真正写文件。
调用图:外部调用 1 个(current_dir)。
ActionKind::policy_src136–148 ↗
fn policy_src(&self) -> Option<&'static str>
作用:从一个测试动作里取出可选的命令规则文本。只有带自定义策略的命令会返回规则,其他动作都没有。
数据流:进去的是一个动作种类 → 它查看这个动作是不是 RunCommandWithPolicy → 出来是规则文本,或者没有规则。
调用关系:run_scenario 在搭测试环境前调用它。如果返回了规则,测试会先把规则文件写进临时 home 目录,让后面的命令审批逻辑按这条规则运行。
ActionKind::prepare150–297 ↗
async fn prepare(
&self,
test: &TestCodex,
server: &MockServer,
call_id: &str,
sandbox_permissions: SandboxPermissions,
) -> Result<(Value, Option<String>)>
作用:把一个抽象测试动作变成模型会发出的工具调用事件。比如“写文件”会被做成一条 shell 命令,“访问网址”会先挂好假服务器响应,“打补丁”会生成补丁文本。
数据流:进去的是动作、测试环境、假服务器、调用编号和沙盒权限 → 它按动作类型生成命令、补丁或网络请求,并清理旧文件、挂载假 HTTP 响应 → 出来是一段 JSON 工具调用事件,以及如果有的话,对应的命令字符串。
调用关系:run_scenario 每跑一个场景都会先调用它。它把活儿分给 build_add_file_patch、shell_apply_patch_command、shell_event、shell_event_with_prefix_rule、exec_command_event 等小工具,让后续测试能像真的模型调用工具一样推进。
调用图:调用 6 个内部函数(ev_apply_patch_custom_tool_call, build_add_file_patch, exec_command_event, shell_apply_patch_command, shell_event, shell_event_with_prefix_rule);外部调用 6 个(given, new, format!, remove_file, method, path)。
build_add_file_patch300–302 ↗
fn build_add_file_patch(patch_path: &str, content: &str) -> String
作用:生成一个“新增文件”的补丁文本。补丁可以理解成一张修改说明,告诉系统要新建哪个文件、写入什么内容。
数据流:进去的是补丁里的路径和文件内容 → 它把它们包进固定的 Begin Patch / Add File / End Patch 格式 → 出来是一整段补丁字符串。
调用关系:ActionKind::prepare 在测试补丁动作时调用它;approving_apply_patch_for_session_skips_future_prompts_for_same_file 也用它做第一次新增文件补丁。
调用图:被 2 处调用(prepare, approving_apply_patch_for_session_skips_future_prompts_for_same_file);外部调用 1 个(format!)。
shell_apply_patch_command304–312 ↗
fn shell_apply_patch_command(patch: &str) -> String
作用:把补丁文本包装成一条可以在 shell 里运行的 apply_patch 命令。这样测试能检查“通过 shell 调补丁工具”时是否需要审批。
数据流:进去的是补丁文本 → 它把文本放进 heredoc,也就是 shell 里一段多行输入 → 出来是一条完整 shell 命令字符串。
调用关系:只被 ActionKind::prepare 用在 ApplyPatchShell 场景里。它连接了“补丁内容”和“命令执行审批”两条测试路径。
shell_event314–327 ↗
fn shell_event(
call_id: &str,
command: &str,
timeout_ms: u64,
sandbox_permissions: SandboxPermissions,
) -> Result<Value>
作用:生成一个普通 shell_command 工具调用事件。它是测试里最常用的包装器,用来让假模型要求 Codex 执行一条 shell 命令。
数据流:进去的是调用编号、命令、超时时间和沙盒权限 → 它把这些交给 shell_event_with_prefix_rule,并且不附加命令前缀规则 → 出来是一段 JSON 事件。
调用关系:ActionKind::prepare 和多项专项网络、zsh 测试都会用它。它本身不组装全部细节,而是把工作转给 shell_event_with_prefix_rule。
调用图:调用 1 个内部函数(shell_event_with_prefix_rule);被 6 处调用(prepare, denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt, env_zsh_script_spawned_by_python_can_request_escalation_under_zsh_fork, matched_prefix_rule_runs_unsandboxed_under_zsh_fork, network_approval_flow_survives_danger_full_access_session_start, network_approval_retry_keeps_deny_read_sandbox_for_escalated_command)。
shell_event_with_prefix_rule329–348 ↗
fn shell_event_with_prefix_rule(
call_id: &str,
command: &str,
timeout_ms: u64,
sandbox_permissions: SandboxPermissions,
prefix_rule: Option<Vec<String>>,
) -> Result<Value>
作用:生成带可选“前缀规则”的 shell_command 工具调用事件。前缀规则就是声明某个命令开头是否可信,例如允许 touch 某个文件。
数据流:进去的是调用编号、命令、超时、沙盒权限和可选前缀规则 → 它组出 JSON 参数;如果请求越过沙盒,就把沙盒权限也写进去;如果有前缀规则,也写进去 → 出来是假模型的 shell_command 调用事件。
调用关系:shell_event 会转调它;ActionKind::prepare 和几个前缀规则专项测试也直接用它。后面的审批逻辑会根据它生成的参数决定是否弹出审批。
调用图:调用 2 个内部函数(ev_function_call, requests_sandbox_override);被 4 处调用(prepare, approving_fallback_rule_for_compound_command_works, invalid_requested_prefix_rule_falls_back_for_compound_command, shell_event);外部调用 2 个(json!, to_string)。
exec_command_event350–370 ↗
fn exec_command_event(
call_id: &str,
cmd: &str,
yield_time_ms: Option<u64>,
sandbox_permissions: SandboxPermissions,
justification: Option<&str>,
) -> Result<Value>
作用:生成 unified exec,也就是新版统一执行命令工具的调用事件。它用来测试新版执行通道在请求提权时是否带上理由并触发审批。
数据流:进去的是调用编号、命令、可选让出时间、沙盒权限和可选理由 → 它把命令写进 JSON;如果要越过沙盒,就加入权限和理由 → 出来是 exec_command 工具调用事件。
调用关系:ActionKind::prepare 在 RunUnifiedExecCommand 场景里调用它。它和 shell_event 类似,但服务的是新版 exec_command 工具。
调用图:调用 2 个内部函数(ev_function_call, requests_sandbox_override);被 1 处调用(prepare);外部调用 2 个(json!, to_string)。
Expectation::verify411–600 ↗
fn verify(&self, test: &TestCodex, result: &CommandResult) -> Result<()>
作用:检查一个场景跑完后是否得到预期结果。它会看退出码、输出文字、文件是否存在、文件内容是否正确。
数据流:进去的是期望、测试环境和命令结果 → 它按期望类型读取文件、比较输出、判断退出码,并在成功后清理部分测试文件 → 如果不符合预期就让测试失败,符合就返回成功。
调用关系:run_scenario 在拿到工具调用结果后最后调用它。它是整个矩阵测试的验收关口,确保“审批行为”不只是流程走了,而是真的产生了正确安全效果。
调用图:外部调用 6 个(assert!, assert_eq!, assert_ne!, read_to_string, remove_file, panic!)。
submit_turn648–684 ↗
async fn submit_turn(
test: &TestCodex,
prompt: &str,
approval_policy: AskForApproval,
sandbox_policy: SandboxPolicy,
) -> Result<()>
作用:向 Codex 提交一轮用户输入,并同时指定本轮要用的审批策略和沙盒策略。它相当于在测试里模拟用户说了一句话。
数据流:进去的是测试环境、提示词、审批策略和沙盒策略 → 它组装 UserInput 操作,把工作目录、审批人、沙盒、模型设置等放进线程设置 → 出来是提交成功或失败的结果,并推动 Codex 开始处理这一轮。
调用关系:run_scenario 和许多专项测试都会调用它。它负责启动一轮对话,后续再由 expect_exec_approval、expect_patch_approval 或等待完成函数观察结果。
调用图:调用 1 个内部函数(local_selections);被 9 处调用(approving_apply_patch_for_session_skips_future_prompts_for_same_file, approving_execpolicy_amendment_persists_policy_and_skips_future_prompts, approving_fallback_rule_for_compound_command_works, compound_command_with_one_safe_command_still_requires_approval, denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt, invalid_requested_prefix_rule_falls_back_for_compound_command, network_approval_flow_survives_danger_full_access_session_start, run_scenario, spawned_subagent_execpolicy_amendment_propagates_to_parent_session);外部调用 2 个(default, vec!)。
parse_result686–724 ↗
fn parse_result(item: &Value) -> CommandResult
作用:把工具调用返回的输出解析成统一的 CommandResult。因为不同工具或模型版本可能返回结构化 JSON,也可能返回普通文本,所以这里做兼容。
数据流:进去的是一项 JSON 输出 → 它先尝试按 JSON 解析退出码和输出;失败后再用正则表达式从普通文本里抠出退出码和输出;再失败就把整段文字当 stdout → 出来是退出码和标准输出。
调用关系:run_scenario 和多个专项测试在检查结果前调用它。它把杂乱的工具返回格式整理成 Expectation::verify 或断言能直接使用的形状。
调用图:被 7 处调用(approving_execpolicy_amendment_persists_policy_and_skips_future_prompts, approving_fallback_rule_for_compound_command_works, denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt, env_zsh_script_spawned_by_python_can_request_escalation_under_zsh_fork, matched_prefix_rule_runs_unsandboxed_under_zsh_fork, network_approval_retry_keeps_deny_read_sandbox_for_escalated_command, run_scenario);外部调用 2 个(new, get)。
expect_exec_approval726–751 ↗
async fn expect_exec_approval(
test: &TestCodex,
expected_command: &str,
) -> ExecApprovalRequestEvent
作用:等待并确认 Codex 发出了“执行命令需要审批”的事件。它还会检查审批事件里的命令是不是测试预期的那条。
数据流:进去的是测试环境和期望命令 → 它等待 ExecApprovalRequest 或 TurnComplete 事件;如果等到审批,就比对命令最后一个参数;如果先完成或事件不对,就让测试失败 → 出来是审批请求对象。
调用关系:run_scenario 和多项命令审批专项测试会用它。拿到审批对象后,测试通常会再提交批准、拒绝或保存规则的决定。
调用图:被 5 处调用(approving_execpolicy_amendment_persists_policy_and_skips_future_prompts, approving_fallback_rule_for_compound_command_works, compound_command_with_one_safe_command_still_requires_approval, invalid_requested_prefix_rule_falls_back_for_compound_command, run_scenario);外部调用 3 个(assert_eq!, wait_for_event, panic!)。
expect_patch_approval753–773 ↗
async fn expect_patch_approval(
test: &TestCodex,
expected_call_id: &str,
) -> ApplyPatchApprovalRequestEvent
作用:等待并确认 Codex 发出了“应用补丁需要审批”的事件。它会检查补丁调用编号是否匹配。
数据流:进去的是测试环境和期望调用编号 → 它等待 ApplyPatchApprovalRequest 或 TurnComplete;如果收到补丁审批,就核对 call_id;如果提前完成或事件不对,就失败 → 出来是补丁审批请求对象。
调用关系:run_scenario 和“本会话批准补丁后不再询问”测试会用它。之后测试会用这个审批请求提交批准或拒绝。
调用图:被 2 处调用(approving_apply_patch_for_session_skips_future_prompts_for_same_file, run_scenario);外部调用 3 个(assert_eq!, wait_for_event, panic!)。
wait_for_completion_without_approval775–791 ↗
async fn wait_for_completion_without_approval(test: &TestCodex)
作用:等待一轮任务结束,并确认中间没有出现命令审批请求。它用于验证“这件事应该自动通过”。
数据流:进去的是测试环境 → 它等待 ExecApprovalRequest 或 TurnComplete → 如果看到完成就正常返回;如果看到审批请求就失败。
调用关系:run_scenario 在 Outcome::Auto 场景里调用它,其他测试也用它确认保存规则后第二次不再弹审批。
调用图:被 5 处调用(approving_execpolicy_amendment_persists_policy_and_skips_future_prompts, approving_fallback_rule_for_compound_command_works, matched_prefix_rule_runs_unsandboxed_under_zsh_fork, run_scenario, spawned_subagent_execpolicy_amendment_propagates_to_parent_session);外部调用 2 个(wait_for_event, panic!)。
wait_for_completion793–798 ↗
async fn wait_for_completion(test: &TestCodex)
作用:单纯等待当前轮次结束。它不判断中间有没有审批,因为调用它之前通常已经处理过审批。
数据流:进去的是测试环境 → 它等待 TurnComplete 事件 → 出来时表示这一轮 Codex 已经完成。
调用关系:run_scenario 在批准或拒绝审批后调用它;许多专项测试也用它收尾,确保再去检查文件或输出时任务已经结束。
调用图:被 9 处调用(approving_apply_patch_for_session_skips_future_prompts_for_same_file, approving_execpolicy_amendment_persists_policy_and_skips_future_prompts, approving_fallback_rule_for_compound_command_works, compound_command_with_one_safe_command_still_requires_approval, denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt, env_zsh_script_spawned_by_python_can_request_escalation_under_zsh_fork, network_approval_flow_survives_danger_full_access_session_start, network_approval_retry_keeps_deny_read_sandbox_for_escalated_command, run_scenario);外部调用 1 个(wait_for_event)。
body_contains800–818 ↗
fn body_contains(req: &Request, text: &str) -> bool
作用:检查假服务器收到的请求正文里是否包含某段文字。它还能处理 zstd 压缩的请求体。
数据流:进去的是 HTTP 请求和要查找的文字 → 它先看请求是否用 zstd 压缩;如果是就解压;再把字节转成 UTF-8 文本并查找 → 出来是真或假。
调用关系:它被用在 mount_sse_once_match 的匹配条件里,帮助专项测试把父代理、子代理、不同轮次的假模型响应准确配到对应请求上。
调用图:调用 1 个内部函数(new);外部调用 1 个(decode_all)。
wait_for_spawned_thread820–839 ↗
async fn wait_for_spawned_thread(test: &TestCodex) -> Result<Arc<CodexThread>>
作用:等待子代理线程被创建出来。子代理可以理解成主会话派出去的另一个小会话。
数据流:进去的是测试环境 → 它在两秒内反复列出线程 ID,找出不是当前主线程的那个 → 找到就取回线程对象,超时就报错。
调用关系:spawned_subagent_execpolicy_amendment_propagates_to_parent_session 用它拿到子代理线程,然后在子线程上等待审批事件。
调用图:被 1 处调用(spawned_subagent_execpolicy_amendment_propagates_to_parent_session);外部调用 5 个(from_millis, from_secs, bail!, now, sleep)。
scenarios841–1839 ↗
fn scenarios() -> Vec<ScenarioSpec>
作用:集中列出审批矩阵里的所有测试场景。每个场景都写明:用什么审批策略、什么沙盒、做什么动作、期望自动通过还是要审批、最后应该看到什么结果。
数据流:进去没有参数 → 它构造大量 ScenarioSpec 对象,包括危险全权限、只读、工作区可写、补丁、新版执行等组合 → 出来是一整个场景列表。
调用关系:run_scenario_group 会调用它,再按分组筛选。它是矩阵测试的数据来源,真正执行由 run_scenario 负责。
调用图:被 1 处调用(run_scenario_group);外部调用 1 个(vec!)。
approval_matrix_covers_group1847–1849 ↗
async fn approval_matrix_covers_group(group: ScenarioGroup) -> Result<()>
作用:这是参数化测试入口之一,用来按组运行审批矩阵。参数化测试就是同一个测试函数用不同输入跑多遍。
数据流:进去的是场景分组 → 它调用 run_scenario_group → 出来是该组全部场景的测试结果。
调用关系:测试框架会自动用 DangerFullAccess、ReadOnly、WorkspaceWrite、ApplyPatch、UnifiedExec 等分组调用它。它把测试入口交给 run_scenario_group。
调用图:调用 1 个内部函数(run_scenario_group)。
run_scenario_group1851–1867 ↗
async fn run_scenario_group(group: ScenarioGroup) -> Result<()>
作用:运行某一类审批场景,比如只跑“工作区可写”相关场景。这样大矩阵可以拆开执行,出问题也更好定位。
数据流:进去的是场景分组 → 它先在无网络环境下跳过,再从 scenarios 里筛出属于该组的场景,逐个调用 run_scenario → 出来是整组成功或第一个失败原因。
调用关系:approval_matrix_covers_group 调它。它负责批量调度,单个场景的搭环境、审批、验证都交给 run_scenario。
调用图:调用 2 个内部函数(run_scenario, scenarios);被 1 处调用(approval_matrix_covers_group);外部调用 2 个(assert!, skip_if_no_network!)。
scenario_group1869–1887 ↗
fn scenario_group(scenario: &ScenarioSpec) -> ScenarioGroup
作用:判断一个场景属于哪个大类。分类依据主要是动作类型和沙盒策略。
数据流:进去的是一个 ScenarioSpec → 它看动作是否是补丁、新版执行,或者普通命令/联网/写文件;普通动作再看沙盒是全权限、只读还是工作区可写 → 出来是 ScenarioGroup。
调用关系:run_scenario_group 用它筛选场景。它不执行测试,只负责把场景放到合适的测试桶里。
run_scenario1889–2055 ↗
async fn run_scenario(scenario: &ScenarioSpec) -> Result<()>
作用:执行一个完整的审批场景。它是矩阵测试的核心流水线:搭环境、伪造模型响应、提交用户输入、处理审批、解析输出、验证结果。
数据流:进去的是一个场景说明 → 它启动假服务器,配置模型、审批策略、沙盒和可选规则;让 ActionKind::prepare 生成工具调用;挂载假 SSE 响应;提交一轮对话;按预期等待自动完成或模拟用户审批;最后解析工具输出并调用 Expectation::verify → 出来是场景通过或失败。
调用关系:run_scenario_group 逐个调用它。它会调用 submit_turn、expect_exec_approval、expect_patch_approval、wait_for_completion、parse_result 等辅助函数,把整个测试故事串起来。
调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, expect_patch_approval, parse_result, submit_turn, wait_for_completion, wait_for_completion_without_approval);被 1 处调用(run_scenario_group);外部调用 4 个(assert_eq!, eprintln!, matches!, vec!)。
approving_apply_patch_for_session_skips_future_prompts_for_same_file2059–2176 ↗
async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file() -> Result<()>
作用:测试“对某个补丁批准本会话有效”后,同一会话里继续修改同一个文件不会再次弹审批。
数据流:进去没有外部参数 → 它建立工作区可写但项目外写入要审批的环境;第一次对工作区外文件打新增补丁并选择 ApprovedForSession;第二次对同一文件打更新补丁 → 出来时文件内容应从 before 变成 after,且第二次没有审批请求。
调用关系:它直接使用 build_add_file_patch、submit_turn、expect_patch_approval、wait_for_completion 等工具。它专门补充矩阵里没有覆盖的“会话级补丁批准缓存”行为。
调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, build_add_file_patch, expect_patch_approval, submit_turn, wait_for_completion);外部调用 9 个(assert!, OutsideWorkspace, wait_for_event, format!, remove_file, panic!, skip_if_no_network!, try_from_path, vec!)。
approving_execpolicy_amendment_persists_policy_and_skips_future_prompts2180–2348 ↗
async fn approving_execpolicy_amendment_persists_policy_and_skips_future_prompts() -> Result<()>
作用:测试用户批准一条命令规则后,规则会写进磁盘,并且之后同样命令不再询问。
数据流:进去没有外部参数 → 它第一次运行 touch allow-prefix.txt,等待命令审批,并选择 ApprovedExecpolicyAmendment;检查开发者消息和 rules/default.rules 文件里写入了规则;再运行同一命令 → 第二次应自动完成且文件创建成功。
调用关系:它调用 ActionKind::prepare、expect_exec_approval、submit_turn、parse_result、wait_for_completion_without_approval 等。它验证审批结果不只是临时放行,还会变成持久策略。
调用图:调用 9 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, submit_turn, wait_for_completion, wait_for_completion_without_approval);外部调用 7 个(new, new_read_only_policy, assert!, assert_eq!, read_to_string, remove_file, vec!)。
spawned_subagent_execpolicy_amendment_propagates_to_parent_session2351–2537 ↗
async fn spawned_subagent_execpolicy_amendment_propagates_to_parent_session() -> Result<()>
作用:测试子代理里批准的命令规则,会传回父会话并影响父会话后续命令。这样多代理协作时安全规则不会只停留在子线程里。
数据流:进去没有外部参数 → 它让父会话通过工具调用创建子代理;子代理运行 touch 命令并请求审批;测试批准规则;确认子代理执行成功;随后父会话再次运行同样命令 → 结果应该不再审批。
调用关系:它用 body_contains 匹配不同请求,用 wait_for_spawned_thread 找到子线程,并用 wait_for_completion_without_approval 验证父会话继承规则。
调用图:调用 8 个内部函数(mount_sse_once, mount_sse_once_match, sse, start_mock_server, test_codex, submit_turn, wait_for_completion_without_approval, wait_for_spawned_thread);外部调用 12 个(from_secs, new, new_read_only_policy, assert!, assert_eq!, wait_for_event_with_timeout, remove_file, json!, panic!, to_string (+2 more))。
env_zsh_script_spawned_by_python_can_request_escalation_under_zsh_fork2541–2696 ↗
async fn env_zsh_script_spawned_by_python_can_request_escalation_under_zsh_fork() -> Result<()>
作用:测试一种比较绕的情况:Python 启动一个带 #!/usr/bin/env zsh 的脚本,脚本里再执行需要提权的 touch,系统仍然能拦住并请求审批。
数据流:进去没有外部参数 → 它准备 zsh-fork 运行环境、受限权限、工作区内脚本和工作区外目标文件;模型调用 Python 执行脚本;测试等待 touch 命令的审批;批准后等待完成 → 输出应包含完成标记,工作区外文件应被创建。
调用关系:它调用 zsh_fork_runtime、build_zsh_fork_test、shell_event、turn_permission_fields、parse_result 等。它覆盖的是普通矩阵不容易触达的“嵌套进程也不能绕过审批”场景。
调用图:调用 11 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, turn_permission_fields, build_zsh_fork_test, restrictive_workspace_write_profile, zsh_fork_runtime, parse_result, shell_event (+1 more));外部调用 16 个(default, from_secs, assert!, assert_eq!, wait_for_event_with_timeout, format!, metadata, set_permissions, write, panic! (+6 more))。
matched_prefix_rule_runs_unsandboxed_under_zsh_fork2700–2799 ↗
async fn matched_prefix_rule_runs_unsandboxed_under_zsh_fork() -> Result<()>
作用:测试 zsh-fork 模式下,如果已有规则允许某个命令前缀,这个命令可以按规则无沙盒重跑并成功写到受限位置。
数据流:进去没有外部参数 → 它写入允许 touch 的规则,准备一个工作区外目标文件;模型发出 touch 命令;测试提交用户输入并等待无审批完成 → 最后确认命令成功且目标文件存在。
调用关系:它使用 build_zsh_fork_test 搭特殊运行环境,用 shell_event 伪造工具调用,用 wait_for_completion_without_approval 验证规则命中后不弹窗。
调用图:调用 11 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, turn_permission_fields, build_zsh_fork_test, restrictive_workspace_write_profile, zsh_fork_runtime, parse_result, shell_event (+1 more));外部调用 8 个(default, assert!, assert_eq!, format!, skip_if_no_network!, current_dir, tempdir_in, vec!)。
invalid_requested_prefix_rule_falls_back_for_compound_command2803–2852 ↗
async fn invalid_requested_prefix_rule_falls_back_for_compound_command() -> Result<()>
作用:测试模型请求的前缀规则如果不适合复杂组合命令,系统会退回成更保守的整条命令审批规则,而不是错误放行。
数据流:进去没有外部参数 → 它构造一条包含 touch 和 echo 重定向的复合命令,同时请求只按 touch 前缀放行;提交后等待审批 → 审批里应给出包含整条命令的规则建议。
调用关系:它直接调用 shell_event_with_prefix_rule、submit_turn、expect_exec_approval。它验证规则生成的安全兜底逻辑。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, shell_event_with_prefix_rule, submit_turn);外部调用 3 个(new_read_only_policy, assert!, vec!)。
approving_fallback_rule_for_compound_command_works2856–2968 ↗
async fn approving_fallback_rule_for_compound_command_works() -> Result<()>
作用:接着测试上一种兜底规则:如果用户批准了这条保守的整命令规则,之后同样复合命令确实能免审批运行。
数据流:进去没有外部参数 → 第一次提交复合命令并批准系统建议的兜底规则;等完成后再次提交相同复合命令 → 第二次应不弹审批并返回成功退出码。
调用关系:它使用 shell_event_with_prefix_rule、expect_exec_approval、wait_for_completion、wait_for_completion_without_approval、parse_result。它证明兜底规则不只是能生成,也能正确生效。
调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, shell_event_with_prefix_rule, submit_turn, wait_for_completion, wait_for_completion_without_approval);外部调用 4 个(new_read_only_policy, assert!, assert_eq!, vec!)。
denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt2971–3249 ↗
async fn denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt() -> Result<()>
作用:测试网络访问审批里选择“拒绝并保存规则”后,拒绝规则会写入规则文件,下一次访问同一主机不会再问。
数据流:进去没有外部参数 → 它开启受管理的网络限制和代理;第一次访问测试域名,等待 network-access 审批;选择 deny 网络规则;检查 rules/default.rules 中出现 deny 规则;第二次访问同域名 → 应直接失败且没有网络审批提示。
调用关系:它调用 shell_event、submit_turn、wait_for_completion、parse_result,并在循环中自动批准非网络审批。它覆盖网络策略持久化这一条安全链路。
调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, parse_result, shell_event, submit_turn, wait_for_completion);外部调用 14 个(new, new, assert!, assert_eq!, managed_network_requirements_loader, wait_for_event_with_timeout, format!, read_to_string, write, panic! (+4 more))。
network_approval_retry_keeps_deny_read_sandbox_for_escalated_command3253–3454 ↗
async fn network_approval_retry_keeps_deny_read_sandbox_for_escalated_command() -> Result<()>
作用:测试带“禁止读取某些文件”的权限配置下,命令请求提权并触发网络审批重试时,不能把原本的拒读限制弄丢。
数据流:进去没有外部参数 → 它构造一个包含 deny read 规则的文件系统沙盒和受限网络;命令请求提权访问网络;先批准外层命令提权,再等待网络审批并批准网络规则 → 最后确认命令有输出,且过程中只出现一次外层命令审批。
调用关系:它直接组装 PermissionProfile,并使用 turn_permission_fields、shell_event、local_selections、parse_result 等。它验证网络审批重试不会意外扩大文件读取权限。
调用图:调用 11 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, parse_result, shell_event, wait_for_completion, from_runtime_permissions (+1 more));外部调用 14 个(new, default, new, assert!, assert_eq!, managed_network_requirements_loader, wait_for_event_with_timeout, format!, write, panic! (+4 more))。
network_approval_flow_survives_danger_full_access_session_start3457–3597 ↗
async fn network_approval_flow_survives_danger_full_access_session_start() -> Result<()>
作用:测试即使会话一开始是危险全权限模式,后续某一轮切到受控网络策略时,网络审批流程仍然能正常工作。
数据流:进去没有外部参数 → 它用全权限启动会话,因此启动时不启用受管理网络代理;随后在某一轮提交工作区网络受限策略并访问测试域名;等待 network-access 审批;拒绝审批并等待完成 → 流程应正常结束。
调用关系:它使用 shell_event、submit_turn、wait_for_completion 等。它专门检查配置从宽松启动切到受限执行时,网络审批不会失效。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, shell_event, submit_turn, wait_for_completion);外部调用 12 个(new, new, assert!, assert_eq!, managed_network_requirements_loader, wait_for_event_with_timeout, write, panic!, skip_if_no_network!, from_secs (+2 more))。
compound_command_with_one_safe_command_still_requires_approval3602–3669 ↗
async fn compound_command_with_one_safe_command_still_requires_approval() -> Result<()>
作用:测试复合命令里即使有一段看起来匹配安全规则,整条命令仍然需要审批。因为 touch && rm 这种组合不能只看第一段就放行。
数据流:进去没有外部参数 → 它写入一条允许特定 touch 的规则;模型发出 touch ./test.txt && rm ./test.txt;提交后等待执行审批;测试拒绝审批并等待完成 → 说明系统没有因为部分命令安全就放过整条命令。
调用关系:它调用 ActionKind::prepare、submit_turn、expect_exec_approval、wait_for_completion。它是对命令规则匹配的保守性检查。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, submit_turn, wait_for_completion);外部调用 5 个(new_workspace_write_policy, create_dir_all, write, skip_if_no_network!, vec!)。
core/tests/suite/exec_policy.rs源码 ↗
这个文件像一套“安全门检查清单”。测试会搭一个假的模型服务器,让服务器假装回复“我要执行这个命令”,然后看 Codex 的执行策略怎么反应。这里重点测几类事:策略文件禁止的命令必须被拦住;Windows 沙箱关掉时,统一执行工具不能偷偷执行只读命令;协作模式下,如果模型传来空命令或只有空白字符的命令,程序不能因为规则匹配结果为空就 panic(panic 就是程序直接崩掉)。文件里有几个小帮手:一个用来拼协作模式配置,一个用来提交用户回合,一个用来检查输出里没有泄露内部断言错误。整体目的不是测试命令本身能不能跑,而是测试“该不该跑、不能跑时怎么安全地拒绝”。
collaboration_mode_for_model28–37 ↗
fn collaboration_mode_for_model(model: String) -> CollaborationMode
作用:这个函数做一个测试用的“协作模式”配置。协作模式可以理解成给模型的一套对话和执行偏好,这里指定模型名,并塞入一段开发者说明,方便测试审批流程。
数据流:进去的是一个模型名字符串 → 函数把它放进 CollaborationMode 结构里,同时设置默认模式、不指定推理强度、加入固定的开发者说明 → 出来的是一份可以提交给测试会话的协作模式配置。
调用关系:几个协作模式相关的测试会先调用它拿到配置,再交给 submit_user_turn 一起提交。它不执行命令,只是帮测试准备一份统一的“模式说明书”。
调用图:被 4 处调用(shell_command_empty_script_with_collaboration_mode_does_not_panic, shell_command_whitespace_script_with_collaboration_mode_does_not_panic, unified_exec_empty_script_with_collaboration_mode_does_not_panic, unified_exec_whitespace_script_with_collaboration_mode_does_not_panic)。
submit_user_turn39–78 ↗
async fn submit_user_turn(
test: &core_test_support::test_codex::TestCodex,
prompt: &str,
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
collaboration_mode
作用:这个函数把一次用户输入提交给测试中的 Codex,并顺便带上审批策略、权限配置、沙箱配置和可选的协作模式。它让多个测试不用重复写一大段提交代码。
数据流:进去的是测试对象、用户提示词、审批方式、权限档案,以及可选协作模式 → 它读取当前测试会话的模型名和工作目录,调用 turn_permission_fields 算出沙箱策略和权限字段,调用 local_selections 填入本地环境选择,然后组装成 Op::UserInput 交给 test.codex.submit → 出来是成功或失败的 Result;成功时,Codex 已经收到这一轮用户请求。
调用关系:多个测试在准备好假服务器回复后,会调用它启动一轮对话。它把权限相关的小计算交给 turn_permission_fields,把本地环境选择交给 local_selections,最后把完整请求交给 Codex 测试实例。
调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 5 处调用(shell_command_empty_script_with_collaboration_mode_does_not_panic, shell_command_whitespace_script_with_collaboration_mode_does_not_panic, unified_exec_disabled_windows_sandbox_rejects_managed_read_only_command, unified_exec_empty_script_with_collaboration_mode_does_not_panic, unified_exec_whitespace_script_with_collaboration_mode_does_not_panic);外部调用 2 个(default, vec!)。
assert_no_matched_rules_invariant80–89 ↗
fn assert_no_matched_rules_invariant(output_item: &Value)
作用:这个函数检查命令执行结果里没有出现一个特定的内部错误信息。这个错误信息表示“匹配规则不能为空”的断言失败,出现它就说明边角情况把程序搞崩或搞乱了。
数据流:进去的是一个 JSON 输出项 → 它从里面取出 output 字符串 → 检查字符串不包含 “invariant failed: matched_rules must be non-empty” → 如果包含就让测试失败;如果不包含就什么也不改,继续通过。
调用关系:空命令和空白命令的测试在拿到函数调用输出后都会调用它。它是最后的验收员,确认 Codex 没把内部规则匹配错误暴露到模型工具输出里。
调用图:被 4 处调用(shell_command_empty_script_with_collaboration_mode_does_not_panic, shell_command_whitespace_script_with_collaboration_mode_does_not_panic, unified_exec_empty_script_with_collaboration_mode_does_not_panic, unified_exec_whitespace_script_with_collaboration_mode_does_not_panic);外部调用 2 个(get, assert!)。
unified_exec_disabled_windows_sandbox_rejects_managed_read_only_command93–161 ↗
async fn unified_exec_disabled_windows_sandbox_rejects_managed_read_only_command() -> Result<()>
作用:这个 Windows 专用测试确认:即使统一执行功能打开了,只要 Windows 沙箱被关掉,只读命令也会被策略拒绝。它防止系统在缺少沙箱保护时误以为命令安全。
数据流:测试先启动假服务器,并配置功能开关:打开 UnifiedExec,关闭 Windows 沙箱和提升权限沙箱 → 假服务器发来一次 exec_command,命令是 cmd.exe /c dir → 测试用 submit_user_turn 提交用户请求,等待这一轮完成 → 最后读取工具调用输出,确认里面同时有原命令和“被策略阻止”的信息。
调用关系:它调用 start_mock_server 和 test_codex 搭测试环境,用 mount_sse_once 和 sse 安排假模型的流式回复,靠 submit_user_turn 触发 Codex 处理,再用 wait_for_event 等待结束。这个测试只在 Windows 编译运行时生效。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, submit_user_turn, read_only);外部调用 4 个(assert!, wait_for_event, json!, vec!)。
execpolicy_blocks_shell_invocation164–256 ↗
async fn execpolicy_blocks_shell_invocation() -> Result<()>
作用:这个测试确认外部策略文件真的能禁止 shell 命令。这里写入一条规则:凡是以 echo 开头的命令都不准执行,然后验证 Codex 会拦下它。
数据流:测试先在临时 Codex 主目录下创建 rules/policy.rules,写入禁止 echo 的规则 → 启动假服务器,让它请求执行 shell_command:echo blocked → 测试提交用户输入,并带上不审批、权限禁用等设置 → 等到 ExecCommandEnd 事件后,检查聚合输出里明确说策略禁止以 echo 开头的命令。
调用关系:它自己展开写了提交请求的完整配置,而不是用 submit_user_turn。过程中用 test_codex 建测试实例,用 local_selections 和 turn_permission_fields 填权限环境,用 mount_sse_once 模拟模型回复,用 wait_for_event 捕捉执行结束和整轮结束。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields);外部调用 6 个(default, assert!, wait_for_event, json!, unreachable!, vec!)。
shell_command_empty_script_with_collaboration_mode_does_not_panic259–311 ↗
async fn shell_command_empty_script_with_collaboration_mode_does_not_panic() -> Result<()>
作用:这个测试检查传统 shell_command 工具在协作模式下收到空命令时不会崩溃。空命令虽然没实际内容,但系统仍然应该安全返回,而不是触发内部断言错误。
数据流:测试启动假服务器,启用 CollaborationModes 功能,并使用 gpt-5.2 模型 → 假服务器请求 shell_command,command 是空字符串 → 测试通过 collaboration_mode_for_model 做协作模式配置,再用 submit_user_turn 提交 → 等待整轮完成后,从假服务器收到的函数输出里取结果,并用 assert_no_matched_rules_invariant 检查没有内部错误。
调用关系:它是协作模式边角情况测试之一。它依赖 mount_sse_once 安排模型请求,依赖 submit_user_turn 触发实际流程,最后把验收交给 assert_no_matched_rules_invariant。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_no_matched_rules_invariant, collaboration_mode_for_model, submit_user_turn);外部调用 3 个(wait_for_event, json!, vec!)。
unified_exec_empty_script_with_collaboration_mode_does_not_panic314–370 ↗
async fn unified_exec_empty_script_with_collaboration_mode_does_not_panic() -> Result<()>
作用:这个测试检查新的统一执行工具 exec_command 在协作模式下收到空命令时不会崩溃。它覆盖的是 UnifiedExec 功能打开后的同一类边角情况。
数据流:测试启动假服务器,打开 UnifiedExec 和 CollaborationModes 两个功能,并使用 gpt-5.2 模型 → 假服务器请求 exec_command,cmd 是空字符串 → 测试创建协作模式配置并提交用户输入 → 等待回合结束后,读取函数调用输出,确认没有出现 matched_rules 为空导致的内部错误。
调用关系:它和 shell_command_empty_script_with_collaboration_mode_does_not_panic 很像,但走的是统一执行路径。它调用 collaboration_mode_for_model 准备模式,调用 submit_user_turn 发起请求,调用 assert_no_matched_rules_invariant 做最终检查。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_no_matched_rules_invariant, collaboration_mode_for_model, submit_user_turn);外部调用 3 个(wait_for_event, json!, vec!)。
shell_command_whitespace_script_with_collaboration_mode_does_not_panic373–425 ↗
async fn shell_command_whitespace_script_with_collaboration_mode_does_not_panic() -> Result<()>
作用:这个测试确认传统 shell_command 收到只有空格、换行、制表符的命令时也不会崩溃。这样的命令看起来有字符,但实际等同于空命令,是容易漏掉的边角情况。
数据流:测试启动假服务器并启用 CollaborationModes → 假服务器请求 shell_command,command 内容只有空白字符 → 测试生成协作模式配置,提交用户请求 → 等待整轮完成 → 从函数调用输出中检查没有出现内部 matched_rules 断言失败信息。
调用关系:它补上了“空白但不完全为空”的测试场景。流程上和空 shell 命令测试一样:假服务器给工具调用,submit_user_turn 送入 Codex,assert_no_matched_rules_invariant 验收输出。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_no_matched_rules_invariant, collaboration_mode_for_model, submit_user_turn);外部调用 3 个(wait_for_event, json!, vec!)。
unified_exec_whitespace_script_with_collaboration_mode_does_not_panic428–484 ↗
async fn unified_exec_whitespace_script_with_collaboration_mode_does_not_panic() -> Result<()>
作用:这个测试确认统一执行工具 exec_command 在协作模式下收到只有空白字符的命令时不会崩溃。它防止新执行路径在处理“看似有内容、实际没命令”的输入时出错。
数据流:测试启动假服务器,打开 UnifiedExec 和 CollaborationModes → 假服务器请求 exec_command,cmd 只有空格、换行和制表符 → 测试构造协作模式并提交用户回合 → 等待 TurnComplete 表示本轮结束 → 读取函数调用输出,确认没有暴露内部 matched_rules 断言错误。
调用关系:它是统一执行路径的空白命令回归测试。它把准备协作模式交给 collaboration_mode_for_model,把提交请求交给 submit_user_turn,把错误检查交给 assert_no_matched_rules_invariant。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_no_matched_rules_invariant, collaboration_mode_for_model, submit_user_turn);外部调用 3 个(wait_for_event, json!, vec!)。
core/tests/suite/skill_approval.rs源码 ↗
这个测试文件像一名安全检查员,专门盯着 Codex 在 zsh fork 运行环境里执行 shell 脚本时,权限边界有没有守住。这里的“沙箱”可以理解成给程序划出的活动围栏:它只能在允许的目录里读写,不能随便碰系统其他地方。文件先搭建一个假的服务器和测试用 Codex 会话,再伪造模型让它调用 shell_command 工具。第一个测试会创建一个带脚本的“技能”,还故意给技能写一份声明,声称它可以写某个目录;测试要确认这个声明不会扩大真实权限,脚本仍然受当前 turn 的沙箱限制。第二个测试更直接:让命令去 /tmp 写文件,确认 WorkspaceWrite 这类工作区写入限制仍然生效。辅助函数负责写技能文件、拼 shell 命令参数、提交用户输入、等待事件和识别常见的权限拒绝输出。
write_skill_metadata27–32 ↗
fn write_skill_metadata(home: &Path, name: &str, contents: &str) -> Result<()>
作用:这个函数给测试用的技能写一份元数据文件。它模拟真实技能目录里会出现的 openai.yaml,用来声明技能信息或权限。
数据流:进去的是一个测试用 home 目录、技能名和 YAML 文本内容;它先拼出 skills/技能名/agents 这个目录,确保目录存在;然后把内容写进 openai.yaml。出来的是成功或失败结果,同时磁盘上多了一份技能元数据文件。
调用关系:它是测试准备阶段的小工具。第一个大测试在搭建 zsh fork 测试环境时通过回调使用它,故意写入技能声明的文件权限,用来验证这些声明不会放大脚本真实权限。
调用图:外部调用 3 个(join, create_dir_all, write)。
shell_command_arguments34–39 ↗
fn shell_command_arguments(command: &str) -> Result<String>
作用:这个函数把一条 shell 命令包装成 shell_command 工具需要的 JSON 参数。这样假模型服务器返回工具调用时,格式就和真实系统期待的一样。
数据流:进去的是一段命令字符串;它把命令和固定的 timeout_ms: 500 放进一个 JSON 对象,再转成字符串;出来的是这段 JSON 字符串,如果序列化失败就返回错误。
调用关系:两个测试都会用它来准备假模型的工具调用参数。它把人类可读的命令变成工具接口能吃的参数,然后交给 mount_function_call_agent_response 挂到假服务器响应里。
调用图:被 2 处调用(shell_zsh_fork_skill_scripts_ignore_declared_permissions, shell_zsh_fork_still_enforces_workspace_write_sandbox);外部调用 2 个(json!, to_string)。
submit_turn_with_policies41–76 ↗
async fn submit_turn_with_policies(
test: &TestCodex,
prompt: &str,
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
) -> Result<()>
作用:这个函数向测试中的 Codex 会话提交一轮用户输入,并同时指定这轮对话要用什么审批策略和权限配置。它让测试可以精确控制“这次运行到底该有多大权限”。
数据流:进去的是测试会话、用户提示词、审批策略和权限档案;它先根据当前工作目录算出沙箱策略和权限字段,再组装成 Op::UserInput 发给 Codex;出来的是提交成功或失败结果,同时 Codex 开始处理这一轮输入。
调用关系:两个大测试都靠它启动真正要验证的那一轮执行。它处在测试准备和事件等待之间:前面已经配置好假服务器和命令,调用它之后,测试再等待执行审批请求或 turn 完成事件。
调用图:调用 3 个内部函数(cwd_path, local_selections, turn_permission_fields);被 2 处调用(shell_zsh_fork_skill_scripts_ignore_declared_permissions, shell_zsh_fork_still_enforces_workspace_write_sandbox);外部调用 2 个(default, vec!)。
write_skill_with_shell_script_contents79–107 ↗
fn write_skill_with_shell_script_contents(
home: &Path,
name: &str,
script_name: &str,
script_contents: &str,
) -> Result<PathBuf>
作用:这个函数创建一个测试技能,并在里面放一个可执行的 shell 脚本。它用来模拟“用户安装了一个带脚本的技能”的场景。
数据流:进去的是 home 目录、技能名、脚本文件名和脚本文本;它创建 skills/技能名/scripts 目录,写入 SKILL.md,再写入脚本文件,并把脚本权限改成可执行;出来的是脚本路径,同时磁盘上出现了完整的测试技能目录。
调用关系:第一个大测试在 build_zsh_fork_test 的初始化回调里使用它。它准备好待执行的技能脚本,后面的 skill_script_command 会再找到这个脚本并生成实际 shell 命令。
调用图:外部调用 6 个(join, format!, create_dir_all, metadata, set_permissions, write)。
skill_script_command109–116 ↗
fn skill_script_command(test: &TestCodex, script_name: &str) -> Result<String>
作用:这个函数找到测试技能里的脚本,并把它变成一条安全可用的 shell 命令字符串。它避免路径里有空格或特殊字符时命令被错误解析。
数据流:进去的是测试会话和脚本名;它从 Codex 的 home 目录下找到 skills/mbolin-test-skill/scripts/脚本名,转成真实绝对路径,再用 shell 转义规则拼成命令;出来的是可以交给 shell_command 执行的命令字符串。
调用关系:第一个大测试用它取得刚刚创建的技能脚本命令。它的输出会交给 shell_command_arguments,再被假模型服务器作为工具调用参数返回。
调用图:调用 1 个内部函数(codex_home_path);被 1 处调用(shell_zsh_fork_skill_scripts_ignore_declared_permissions);外部调用 2 个(canonicalize, try_join)。
wait_for_exec_approval_request118–125 ↗
async fn wait_for_exec_approval_request(test: &TestCodex) -> Option<ExecApprovalRequestEvent>
作用:这个函数等待 Codex 发出“要不要批准执行命令”的事件。如果这一轮直接结束而没有请求批准,它会返回空值。
数据流:进去的是测试会话;它持续观察 Codex 发出的事件,看到 ExecApprovalRequest 就复制请求返回,看到 TurnComplete 就返回 None;出来的是一个可选的执行审批请求。
调用关系:第一个大测试用它确认旧的“技能审批”路径没有被触发。也就是说,技能脚本不应该先跑到一个已经移除或不该使用的审批关卡,而应该直接受 turn 沙箱管。
调用图:被 1 处调用(shell_zsh_fork_skill_scripts_ignore_declared_permissions);外部调用 1 个(wait_for_event_match)。
wait_for_turn_complete127–132 ↗
async fn wait_for_turn_complete(test: &TestCodex)
作用:这个函数等待当前一轮 Codex 处理结束。测试需要等它结束后,才能安全检查工具输出和文件系统结果。
数据流:进去的是测试会话;它监听事件,直到看到 TurnComplete;出来时没有额外数据,但表示这一轮已经跑完。
调用关系:两个大测试都会在提交用户输入后调用它。它像一个同步点:先让 Codex 和工具调用完成,再让测试读取假服务器记录的输出并检查文件有没有被写出来。
调用图:被 2 处调用(shell_zsh_fork_skill_scripts_ignore_declared_permissions, shell_zsh_fork_still_enforces_workspace_write_sandbox);外部调用 1 个(wait_for_event)。
output_shows_sandbox_denial134–138 ↗
fn output_shows_sandbox_denial(output: &str) -> bool
作用:这个函数判断一段命令输出看起来是不是被沙箱拦住了。不同系统的报错文字不完全一样,所以它检查几种常见说法。
数据流:进去的是命令输出文本;它查找是否包含 Permission denied、Operation not permitted 或 Read-only file system;出来的是 true 或 false,表示输出是否像权限拒绝。
调用关系:两个大测试都用它判断 shell 命令是否真的被权限边界挡住。它把系统差异藏起来,让测试不用只依赖某一个固定报错句子。
shell_zsh_fork_skill_scripts_ignore_declared_permissions142–233 ↗
async fn shell_zsh_fork_skill_scripts_ignore_declared_permissions() -> Result<()>
作用:这个测试确认:技能脚本自己声明的文件写入权限不会扩大真实执行权限。换句话说,技能不能靠写一份权限声明,就突破当前会话的沙箱。
数据流:它先准备 zsh fork 运行时、审批策略、受限的工作区写入配置和一个工作区外的目标文件;然后创建带脚本的测试技能,并给技能写入“允许写某目录”的声明;接着让假模型调用这个脚本。测试提交用户输入后,检查没有出现执行审批请求,等待执行完成,再读取工具输出并确认目标文件没有被写出来。
调用关系:这是本文件的核心安全回归测试之一。它串起 start_mock_server、build_zsh_fork_test、write_skill_with_shell_script_contents、write_skill_metadata、skill_script_command、shell_command_arguments、submit_turn_with_policies、wait_for_exec_approval_request 和 wait_for_turn_complete,完整模拟一次技能脚本执行。
调用图:调用 10 个内部函数(mount_function_call_agent_response, start_mock_server, build_zsh_fork_test, restrictive_workspace_write_profile, zsh_fork_runtime, shell_command_arguments, skill_script_command, submit_turn_with_policies, wait_for_exec_approval_request, wait_for_turn_complete);外部调用 8 个(Granular, assert!, format!, create_dir_all, try_join, skip_if_no_network!, current_dir, tempdir_in)。
shell_zsh_fork_still_enforces_workspace_write_sandbox237–291 ↗
async fn shell_zsh_fork_still_enforces_workspace_write_sandbox() -> Result<()>
作用:这个测试确认 zsh fork 环境仍然会执行工作区写入限制。即使命令只是简单 touch 一个工作区外文件,也应该被拦下。
数据流:它先准备 zsh fork 运行时、假服务器、受限的 WorkspaceWrite 权限档案和一个 /tmp 下的目标路径;然后让假模型返回 touch 这个路径的 shell_command 调用;提交用户输入并等待 turn 完成后,它读取命令输出,确认出现沙箱拒绝信息,并确认目标文件没有生成。
调用关系:这是对整体沙箱行为的基础保护测试。它和第一个测试使用相同的测试骨架:假服务器提供工具调用,submit_turn_with_policies 启动一轮执行,wait_for_turn_complete 等结束,最后用 output_shows_sandbox_denial 和文件存在性检查安全边界是否生效。
调用图:调用 8 个内部函数(mount_function_call_agent_response, start_mock_server, build_zsh_fork_test, restrictive_workspace_write_profile, zsh_fork_runtime, shell_command_arguments, submit_turn_with_policies, wait_for_turn_complete);外部调用 4 个(assert!, format!, remove_file, skip_if_no_network!)。
core/tests/suite/unified_exec_zsh_fork_approvals.rs源码 ↗
这份测试文件像一套安全闸门演练。系统会让模型发起一个 exec_command,也就是“请帮我在机器上跑一条 shell 命令”。命令可能需要用户批准,批准后还要进入沙盒(一个限制程序能读写哪里、能不能联网的隔离环境)。这里重点测 zsh-fork 运行方式:命令实际通过 zsh 这个 shell 的 fork 机制执行,容易在父进程批准、子进程真正执行之间弄错权限。三个主测试分别确认:批准后仍不能读取被明确禁止的文件;批准后可以按预期提升权限去写工作区外的文件;如果本地规则明确写着某类命令要再问一次,系统不能因为父命令已批准就跳过这次询问。辅助函数负责搭测试环境、伪造模型服务返回、提交用户输入、批准请求、解析命令结果。整体价值是守住“批准可以放行该放行的事,但不能扩大到不该放行的事”这条安全线。
unified_exec_zsh_fork_parent_approval_preserves_denied_reads52–111 ↗
async fn unified_exec_zsh_fork_parent_approval_preserves_denied_reads() -> Result<()>
作用:这个测试确认:用户批准执行命令以后,命令仍然不能读取权限配置里明确禁止读取的文件。它是在防止“批准运行命令”被误解成“什么文件都能看”。
数据流:它先创建一个临时秘密文件,里面写入测试用密文;再生成一份权限配置,把这个文件标成禁止读取;然后让模拟的模型服务请求执行 cat 读取该文件。测试提交一次用户输入,等系统发出执行审批请求后批准它,最后读取命令结果。结果应该是退出码非零,并且输出里不能出现秘密内容。
调用关系:这是顶层测试之一。它用 denied_read_permission_profile 做禁止读取的权限档案,用 build_unified_exec_zsh_fork_test_or_skip 搭环境,用 mount_unified_exec_command 伪造模型发来的命令,再通过 submit_turn_with_session_permissions 触发一轮对话,接着交给 approve_expected_exec 批准父级命令,最后用 wait_for_completion_without_approval 和 command_result 检查收尾结果。
调用图:调用 7 个内部函数(approve_expected_exec, build_unified_exec_zsh_fork_test_or_skip, command_result, denied_read_permission_profile, mount_unified_exec_command, submit_turn_with_session_permissions, wait_for_completion_without_approval);外部调用 7 个(assert!, assert_ne!, format!, write, skip_if_no_network!, current_dir, tempdir_in)。
unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec114–172 ↗
async fn unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec() -> Result<()>
作用:这个测试确认:当命令请求提升权限并且用户批准后,通过 zsh-fork 执行的 shell 重定向确实能获得该权限。它保证批准不是摆设,应该放行的写文件动作真的能成功。
数据流:它先准备一个工作区外的临时文件路径,并生成一条 printf hi > 文件 的命令;默认权限较严格,正常不能写那里。测试环境启动后,模拟模型服务要求执行这条命令并声明需要提升沙盒权限。用户输入触发执行,测试批准审批请求,然后等待结束。最后它从模拟结果里看退出码应为 0,并从磁盘读取目标文件,确认内容正好是 hi。
调用关系:这是顶层测试之一。它使用 restrictive_workspace_write_profile 提供严格权限,用 build_unified_exec_zsh_fork_test_or_skip 创建可测试的 zsh-fork 环境,用 mount_unified_exec_command 安排模型返回的执行请求。审批流程由 submit_turn_with_session_permissions、approve_expected_exec 和 wait_for_completion_without_approval 串起来,结果由 command_result 和实际文件读取共同验证。
调用图:调用 7 个内部函数(restrictive_workspace_write_profile, approve_expected_exec, build_unified_exec_zsh_fork_test_or_skip, command_result, mount_unified_exec_command, submit_turn_with_session_permissions, wait_for_completion_without_approval);外部调用 6 个(assert_eq!, format!, read_to_string, skip_if_no_network!, current_dir, tempdir_in)。
unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule175–264 ↗
async fn unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule() -> Result<()>
作用:这个测试确认:即使父级 unified exec 命令已经被用户批准,如果本地规则明确要求某个具体命令还要询问,系统仍然必须再次弹出审批。它防止安全规则被父级批准“盖过去”。
数据流:它创建一个工作区外的目标文件,命令是 touch 文件;同时在测试用 home 目录里写入规则文件,规定以 touch 开头的命令必须提示用户。模拟模型先请求执行父级命令,测试批准第一次审批;随后它等待下一条事件,期望看到针对真正 touch 命令的第二次审批请求。测试再批准第二次请求,等待整轮结束,最后确认命令成功并且文件已经被创建。
调用关系:这是最复杂的顶层测试。它和前两个测试一样依赖 build_unified_exec_zsh_fork_test_or_skip、mount_unified_exec_command、submit_turn_with_session_permissions 和 approve_expected_exec,但在父级审批后还直接等待一个新的 ExecApprovalRequest。第二次审批通过 approve_exec 完成,最终由 wait_for_completion 和 command_result 确认流程结束且结果正确。
调用图:调用 8 个内部函数(restrictive_workspace_write_profile, approve_exec, approve_expected_exec, build_unified_exec_zsh_fork_test_or_skip, command_result, mount_unified_exec_command, submit_turn_with_session_permissions, wait_for_completion);外部调用 9 个(from_secs, assert!, assert_eq!, wait_for_event_with_timeout, format!, panic!, skip_if_no_network!, current_dir, tempdir_in)。
build_unified_exec_zsh_fork_test_or_skip271–294 ↗
async fn build_unified_exec_zsh_fork_test_or_skip(
test_name: &str,
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
pre_build_hook: F,
) -> Result<Option<(MockS
作用:这个辅助函数负责搭好一整套 zsh-fork 测试环境;如果当前机器不支持这种运行环境,就安全地跳过测试。它让每个测试不用重复写启动模拟服务器、创建测试 Codex 实例这些准备工作。
数据流:输入是测试名字、审批策略、权限档案,以及一个构建前可执行的小钩子。它先询问 zsh_fork_runtime 能不能拿到 zsh-fork 运行时;拿不到就返回 None。拿到后启动一个模拟服务器,再调用测试支持库创建 TestCodex,最后返回服务器和测试实例。
调用关系:三个顶层测试都会先调用它。它把底层准备工作交给 start_mock_server 和 build_unified_exec_zsh_fork_test,上层测试拿到结果后才开始挂载模拟响应、提交用户输入和检查审批行为。
调用图:调用 3 个内部函数(start_mock_server, build_unified_exec_zsh_fork_test, zsh_fork_runtime);被 3 处调用(unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec, unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule, unified_exec_zsh_fork_parent_approval_preserves_denied_reads)。
denied_read_permission_profile296–309 ↗
fn denied_read_permission_profile(denied_path: &Path) -> Result<PermissionProfile>
作用:这个辅助函数生成一份“某个指定文件禁止读取”的权限档案。它专门服务于测试秘密文件不能被批准后的命令读出来这个场景。
数据流:输入是要禁止读取的文件路径。它把路径转成 TOML 配置里的键,拼出一段权限配置文本:根目录可读、项目根可写、这个指定路径拒绝访问、网络关闭。然后把这段文本交给 permission_profile_from_toml 转成程序真正使用的权限对象。
调用关系:它只被 unified_exec_zsh_fork_parent_approval_preserves_denied_reads 使用。它不直接参与执行命令,而是在测试开始前准备好沙盒规则,让后面的 cat secret.env 能验证禁止读取是否仍然生效。
调用图:调用 1 个内部函数(permission_profile_from_toml);被 1 处调用(unified_exec_zsh_fork_parent_approval_preserves_denied_reads);外部调用 3 个(to_string_lossy, new, format!)。
permission_profile_from_toml311–355 ↗
fn permission_profile_from_toml(profile: &str) -> Result<PermissionProfile>
作用:这个函数把测试里写成文本的权限配置,转换成系统运行时真正认识的权限档案。这样测试可以用接近用户配置文件的写法来描述沙盒规则。
数据流:输入是一段 TOML 文本,也就是一种常见的配置文件格式。它先反序列化成测试配置结构,再逐条读取文件系统权限:根目录、项目根目录、拒绝访问的路径会被转成内部的沙盒条目;网络开关会被转成网络沙盒策略。最后它调用 PermissionProfile::from_runtime_permissions 生成最终的 PermissionProfile。
调用关系:它目前由 denied_read_permission_profile 调用。它位于“人能读的测试配置”和“系统能执行的权限对象”之间,帮助顶层测试不用手写一堆底层权限结构。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);被 1 处调用(denied_read_permission_profile)。
mount_unified_exec_command357–392 ↗
async fn mount_unified_exec_command(
server: &MockServer,
response_prefix: &str,
call_id: &str,
command: &str,
justification: &str,
) -> Result<ResponseMock>
作用:这个辅助函数把模拟模型服务的返回内容安排好,让系统以为模型要求执行一条 unified exec 命令。它是测试里“假装远端模型说:请运行这条命令”的部分。
数据流:输入是模拟服务器、响应名前缀、函数调用编号、实际命令和申请提升权限的理由。它先构造第一段流式响应:创建响应、发出 exec_command 函数调用、结束响应;然后构造第二段响应:助手说 done 并结束。它把这些响应挂到模拟服务器上,并返回第二段响应的记录对象,供后面检查命令输出。
调用关系:三个顶层测试都用它来安排模型侧行为。它内部调用 exec_command_event 生成函数调用事件,再用 mount_sse_once 和 sse 挂到模拟服务器;之后测试通过 command_result 从它返回的 ResponseMock 里取执行结果。
调用图:调用 3 个内部函数(mount_sse_once, sse, exec_command_event);被 3 处调用(unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec, unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule, unified_exec_zsh_fork_parent_approval_preserves_denied_reads);外部调用 2 个(format!, vec!)。
submit_turn_with_session_permissions394–433 ↗
async fn submit_turn_with_session_permissions(
test: &TestCodex,
prompt: &str,
approval_policy: AskForApproval,
) -> Result<()>
作用:这个函数向测试中的 Codex 会话提交一条用户输入,并把本轮对话需要用的权限、审批策略和模型设置一起带上。它模拟真实用户说了一句话后,系统开始处理这一轮请求。
数据流:输入是测试实例、用户提示词和审批策略。它从测试会话里读取当前模型和权限档案,把权限档案转换成本轮请求字段,再创建一个 Op::UserInput 操作,里面包含文本、环境选择、审批策略、审批人、沙盒策略、权限档案和协作模式。最后它把这个操作提交给测试中的 Codex。
调用关系:三个顶层测试在挂好模型响应后都会调用它来启动一轮处理。它不自己等待结果;提交之后,后续审批由 approve_expected_exec 或 approve_exec 处理,完成事件由等待函数接手。
调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 3 处调用(unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec, unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule, unified_exec_zsh_fork_parent_approval_preserves_denied_reads);外部调用 2 个(default, vec!)。
approve_expected_exec435–438 ↗
async fn approve_expected_exec(test: &TestCodex, expected_command: &str) -> Result<()>
作用:这个辅助函数等待系统发出预期的执行审批请求,并立刻批准它。它把“确认确实是在问这条命令”和“点批准”合成一个常用动作。
数据流:输入是测试实例和预期的命令字符串。它先调用 expect_exec_approval 等待并检查审批请求是否对应这条父级 unified exec 命令;拿到审批编号后,再调用 approve_exec 提交批准决定。输出是成功或错误结果。
调用关系:三个顶层测试都用它批准第一次、父级的执行请求。它把等待检查交给 expect_exec_approval,把实际提交批准交给 approve_exec,让测试主体更像人在读的流程:先等审批,再批准。
调用图:调用 2 个内部函数(approve_exec, expect_exec_approval);被 3 处调用(unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec, unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule, unified_exec_zsh_fork_parent_approval_preserves_denied_reads)。
approve_exec440–449 ↗
async fn approve_exec(test: &TestCodex, approval_id: String) -> Result<()>
作用:这个函数向测试中的 Codex 提交“批准执行”的决定。它相当于测试自动点击了审批弹窗里的“允许”。
数据流:输入是测试实例和审批编号。它构造一个 Op::ExecApproval 操作,填入编号、无特定 turn_id、决定为 Approved,然后提交给 Codex。成功后不返回额外数据,只表示批准动作已经送达。
调用关系:它被 approve_expected_exec 用来批准父级命令,也被第三个测试直接用来批准显式规则触发的第二次审批。它是测试流程里真正把审批结果发回系统的出口。
调用图:被 2 处调用(approve_expected_exec, unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule)。
command_result451–453 ↗
fn command_result(results: &ResponseMock, call_id: &str) -> CommandResult
作用:这个函数从模拟服务器记录到的函数调用输出里,取出某次命令执行的结果。它让测试能方便地检查退出码和标准输出。
数据流:输入是 ResponseMock 和函数调用编号。它先找到那一次请求里的函数调用输出,再把这段原始值交给 parse_result 解析。输出是一个 CommandResult,里面有退出码和 stdout 文本。
调用关系:三个顶层测试都在命令执行结束后调用它。它位于测试断言之前,负责把较复杂的模拟响应格式整理成容易判断的结构。
调用图:调用 2 个内部函数(single_request, parse_result);被 3 处调用(unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec, unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule, unified_exec_zsh_fork_parent_approval_preserves_denied_reads)。
exec_command_event455–474 ↗
fn exec_command_event(
call_id: &str,
cmd: &str,
yield_time_ms: Option<u64>,
sandbox_permissions: SandboxPermissions,
justification: &str,
) -> Result<Value>
作用:这个函数生成一条“模型调用 exec_command 工具”的事件。它把命令、超时时间、是否请求沙盒提升和理由包装成测试服务器能发送的 JSON 事件。
数据流:输入是调用编号、命令字符串、可选等待时间、沙盒权限要求和理由。它先组装 JSON 参数;如果权限要求表示需要沙盒覆盖,就额外写入 sandbox_permissions 和 justification。然后把参数转成字符串,交给 ev_function_call 生成最终事件值。
调用关系:它只被 mount_unified_exec_command 调用。mount_unified_exec_command 负责安排整段模拟响应,而它专门负责其中最关键的一块:让系统看到模型要求执行命令。
调用图:调用 2 个内部函数(ev_function_call, requests_sandbox_override);被 1 处调用(mount_unified_exec_command);外部调用 2 个(json!, to_string)。
parse_result476–501 ↗
fn parse_result(item: &Value) -> CommandResult
作用:这个函数把命令执行输出解析成统一的 CommandResult。它兼容两种格式:标准 JSON 格式,以及一些旧式的纯文本格式。
数据流:输入是一段 JSON 值,通常来自函数调用输出。它先尝试读取其中的 output 字符串;没有的话就返回空结果。拿到字符串后,它优先按 JSON 解析,从里面取 metadata.exit_code 和 output;如果不是 JSON,就用两个正则表达式尝试从文本里抓退出码和输出。都失败时,它保留原始输出,退出码为空。
调用关系:它由 command_result 调用,是结果整理的核心。它在 JSON 解析失败时会把工作交给 parsed_regex_result,这样测试不用关心底层输出格式有没有变化。
调用图:调用 1 个内部函数(parsed_regex_result);被 1 处调用(command_result);外部调用 2 个(new, get)。
parsed_regex_result503–512 ↗
fn parsed_regex_result(pattern: &str, output_str: &str) -> Option<CommandResult>
作用:这个小函数用正则表达式从纯文本结果里提取退出码和输出内容。正则表达式可以理解成“带通配符的文本查找规则”。
数据流:输入是一条正则模式和一段输出文本。它先编译正则,再在文本里匹配;如果匹配成功,就把第一组内容转成数字退出码,把第二组内容当作输出。成功时返回 CommandResult,任何一步失败都返回 None。
调用关系:它只被 parse_result 调用,作为 JSON 解析失败后的兜底办法。parse_result 会尝试不同的文本格式,靠它把旧格式也变成统一结果。
调用图:被 1 处调用(parse_result);外部调用 1 个(new)。
expect_exec_approval514–542 ↗
async fn expect_exec_approval(
test: &TestCodex,
expected_command: &str,
) -> ExecApprovalRequestEvent
作用:这个函数等待系统发出执行审批请求,并确认请求里的命令正是测试预期的那条父级命令。它防止测试误把别的审批当成正确审批。
数据流:输入是测试实例和预期命令。它等待事件流中出现执行审批请求或整轮完成事件;如果先等到审批请求,就检查该请求命令的最后一个参数是否等于预期命令,然后返回这个审批事件。如果还没审批就完成了,或者来了别的事件,它会让测试失败。
调用关系:它被 approve_expected_exec 调用,位于“提交用户输入”之后、“提交批准”之前。它负责确认系统确实在问对的事情,随后 approve_expected_exec 会把审批编号交给 approve_exec。
调用图:被 1 处调用(approve_expected_exec);外部调用 3 个(assert_eq!, wait_for_event, panic!)。
wait_for_completion_without_approval544–560 ↗
async fn wait_for_completion_without_approval(test: &TestCodex)
作用:这个函数等待一轮对话结束,并且确认期间没有再出现新的执行审批请求。它用于那些只应该有一次父级审批的测试。
数据流:输入是测试实例。它等待事件流里出现执行审批请求或完成事件;如果是完成事件,就正常返回;如果是新的审批请求,就直接让测试失败,并打印那条意外命令。
调用关系:前两个顶层测试在批准父级命令后调用它。它验证流程后半段没有多余的审批弹窗;这对确认“批准后直接执行完成”很重要。
调用图:被 2 处调用(unified_exec_zsh_fork_parent_approval_escalates_intercepted_exec, unified_exec_zsh_fork_parent_approval_preserves_denied_reads);外部调用 2 个(wait_for_event, panic!)。
wait_for_completion562–567 ↗
async fn wait_for_completion(test: &TestCodex)
作用:这个函数只负责等到当前一轮对话完成。它不检查中途审批,因为调用它的测试已经手动处理了需要的审批。
数据流:输入是测试实例。它持续等待事件流,直到看到 TurnComplete,也就是这一轮处理完成的信号。它不返回额外数据。
调用关系:它被第三个顶层测试使用。第三个测试先自己等待并批准第二次显式规则审批,然后调用它收尾,确认系统最终完成。
调用图:被 1 处调用(unified_exec_zsh_fork_parent_approval_keeps_explicit_prompt_rule);外部调用 1 个(wait_for_event)。
权限请求流程
这些文件覆盖权限如何通过内联和独立的工具驱动路径被请求、授予、持久化,并在之后使用。
core/tests/suite/request_permissions.rs源码 ↗
这份测试像一套“门禁演练”。测试会启动一个假的模型服务器,让模型发出 shell_command、exec_command 或 request_permissions 这些工具调用,然后观察 Codex 是否按规则弹出审批、是否正确放行或拒绝。这里重点测文件写入权限:比如只读模式下,模型请求写某个目录,系统必须先问用户;用户拒绝时命令不能执行;用户只批准一部分时,剩下的新权限仍要再审批。它还检查相对路径会按工具的工作目录来解释,避免“看起来是当前目录,实际写到别处”的误会。文件里有不少小工具函数,用来造假的服务器事件、提交一轮用户输入、等待完成事件、解析命令输出。整体作用不是实现权限功能,而是守住权限功能的安全边界,防止以后改代码时把这些细节改坏。
absolute_path46–48 ↗
fn absolute_path(path: &Path) -> AbsolutePathBuf
作用:把普通路径转换成项目里要求的“绝对路径”类型。测试里权限列表必须用绝对路径,这个函数让写测试时少写重复代码。
数据流:输入一个文件或目录路径 → 调用路径类型的转换方法检查它确实是绝对路径 → 输出一个 AbsolutePathBuf;如果输入不是绝对路径,测试会直接失败。
调用关系:它是很多权限构造步骤里的小帮手,常被 requested_directory_write_permissions 和各个测试用来准备“允许写哪里”的数据。
调用图:调用 1 个内部函数(try_from)。
parse_result55–92 ↗
fn parse_result(item: &Value) -> CommandResult
作用:把工具调用返回的命令结果读成人能判断的结构:退出码和标准输出。不同版本的输出格式可能不一样,所以它同时兼容 JSON 和老式文本格式。
数据流:输入一段函数调用输出的 JSON 值 → 先尝试把里面的 output 当结构化 JSON 解析;不行就用正则表达式从文本里抠出退出码和输出 → 返回 CommandResult,里面有 exit_code 和 stdout。
调用关系:多个测试在命令跑完后调用它,判断命令到底成功没、有没有写出预期内容。它内部用正则表达式作为兜底解析方式。
调用图:被 12 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write, read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write, relative_additional_permissions_resolve_against_tool_workdir, request_permissions_grants_apply_to_later_exec_command_calls, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns, with_additional_permissions_denied_approval_blocks_execution (+2 more));外部调用 2 个(new, get)。
shell_event_with_request_permissions94–107 ↗
fn shell_event_with_request_permissions(
call_id: &str,
command: &str,
additional_permissions: &S,
) -> Result<Value>
作用:造一个假的 shell_command 工具调用事件,并在事件里带上“我需要额外权限”的声明。这样测试可以模拟模型请求临时扩权后再执行 shell 命令。
数据流:输入调用编号、命令字符串、额外权限对象 → 打包成 JSON 参数,标记 sandbox_permissions 为 WithAdditionalPermissions → 输出一个模型函数调用事件。
调用关系:需要测试内联额外权限的用例会用它,比如审批通过、审批拒绝、只读模式不应扩大写权限等场景。它把具体事件交给 ev_function_call 来生成。
调用图:调用 1 个内部函数(ev_function_call);被 5 处调用(read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write, read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write, with_additional_permissions_denied_approval_blocks_execution, with_additional_permissions_requires_approval_under_on_request, workspace_write_with_additional_permissions_can_write_outside_cwd);外部调用 2 个(json!, to_string)。
request_permissions_tool_event109–120 ↗
fn request_permissions_tool_event(
call_id: &str,
reason: &str,
permissions: &RequestPermissionProfile,
) -> Result<Value>
作用:造一个假的 request_permissions 工具调用事件,也就是模型单独说“请给我这些权限”。它用来测试独立的申请权限工具。
数据流:输入调用编号、申请理由、权限内容 → 组成 JSON 参数 → 输出一个 request_permissions 的函数调用事件。
调用关系:测试独立申请权限流程时会用它,随后测试会等待系统发出 RequestPermissions 事件,模拟用户批准或拒绝。
调用图:调用 1 个内部函数(ev_function_call);被 1 处调用(request_permissions_tool_is_auto_denied_when_granular_request_permissions_is_disabled);外部调用 2 个(json!, to_string)。
shell_command_event122–129 ↗
fn shell_command_event(call_id: &str, command: &str) -> Result<Value>
作用:造一个普通的 shell_command 工具调用事件,不带额外权限声明。它用来检查之前批准过的权限,能不能被后面的 shell 命令使用。
数据流:输入调用编号和命令 → 打包命令和超时时间 → 输出一个 shell_command 函数调用事件。
调用关系:在“先申请权限、后执行 shell 命令”的测试里出现,和 request_permissions_tool_event 配合验证授权是否在本轮内生效。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
exec_command_event131–138 ↗
fn exec_command_event(call_id: &str, command: &str) -> Result<Value>
作用:造一个普通的 exec_command 工具调用事件。exec_command 是另一种执行命令的工具,测试要确认它和 shell_command 一样遵守权限规则。
数据流:输入调用编号和命令 → 打包 cmd 和 yield_time_ms 参数 → 输出一个 exec_command 函数调用事件。
调用关系:在测试后续 exec 命令能否使用已批准权限时使用。它把事件生成的细节交给 ev_function_call。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
exec_command_event_with_request_permissions140–153 ↗
fn exec_command_event_with_request_permissions(
call_id: &str,
command: &str,
additional_permissions: &S,
) -> Result<Value>
作用:造一个带额外权限申请的 exec_command 工具调用事件。它模拟模型一边执行命令,一边明确说明需要哪些额外权限。
数据流:输入调用编号、命令、额外权限 → 写入 cmd、等待时间、沙箱权限标记和 additional_permissions → 输出 exec_command 函数调用事件。
调用关系:用在测试“明确申请的执行权限是否能被预先批准”以及“部分批准不能自动放行新权限”等场景。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
exec_command_event_with_missing_additional_permissions155–166 ↗
fn exec_command_event_with_missing_additional_permissions(
call_id: &str,
command: &str,
) -> Result<Value>
作用:造一个故意缺少 additional_permissions 的 exec_command 事件。它用来检查系统发现参数不完整时是否会拒绝,而不是默认放宽权限。
数据流:输入调用编号和命令 → 只写入 WithAdditionalPermissions 标记,但不提供具体额外权限 → 输出一个格式有缺口的 exec_command 事件。
调用关系:在跨轮权限不应自动沿用的测试里使用,用来确认第二轮没有带权限时会收到错误提示。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
submit_turn168–205 ↗
async fn submit_turn(
test: &TestCodex,
prompt: &str,
approval_policy: AskForApproval,
permission_profile: CorePermissionProfile,
) -> Result<()>
作用:向测试里的 Codex 会话提交一轮用户输入,并同时指定审批策略和基础权限档案。它相当于按下“开始这一轮对话”的按钮。
数据流:输入测试环境、用户提示词、审批策略、权限档案 → 根据当前工作目录算出沙箱策略和权限字段,填入线程设置 → 把 Op::UserInput 发送给 Codex;成功时不返回额外数据。
调用关系:几乎每个测试都会先搭好假服务器响应,再调用它启动流程。它依赖 local_selections 和 turn_permission_fields 来把测试配置转换成真实提交需要的字段。
调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 14 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write, read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write, relative_additional_permissions_resolve_against_tool_workdir, request_permissions_grants_apply_to_later_exec_command_calls, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_grants_do_not_carry_across_turns, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns (+4 more));外部调用 2 个(default, vec!)。
wait_for_completion207–212 ↗
async fn wait_for_completion(test: &TestCodex)
作用:等待当前测试回合结束。测试需要它来确认所有命令、审批和模型消息都处理完了,再检查结果。
数据流:输入测试环境 → 持续监听 Codex 事件 → 等到 TurnComplete 事件后结束;它不改变外部数据,只是同步测试节奏。
调用关系:审批完成后或不需要审批时,很多测试都会调用它作为收尾。它把等待事件的底层工作交给 wait_for_event。
调用图:被 13 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write, read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write, relative_additional_permissions_resolve_against_tool_workdir, request_permissions_grants_apply_to_later_exec_command_calls, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_grants_do_not_carry_across_turns, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns (+3 more));外部调用 1 个(wait_for_event)。
expect_exec_approval214–239 ↗
async fn expect_exec_approval(
test: &TestCodex,
expected_command: &str,
) -> ExecApprovalRequestEvent
作用:等待并确认系统真的弹出了“是否允许执行命令”的审批请求。它还会检查审批请求里的命令是不是测试预期的那条。
数据流:输入测试环境和预期命令 → 等待 ExecApprovalRequest 或 TurnComplete → 如果收到审批请求,就比较命令最后一段;如果提前完成或事件不对,就让测试失败 → 返回审批请求对象。
调用关系:需要人工审批的测试会用它拿到审批编号,然后测试再提交 Approved 或 Denied。它是验证“必须先问用户”的关键关卡。
调用图:被 7 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write, read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write, relative_additional_permissions_resolve_against_tool_workdir, with_additional_permissions_denied_approval_blocks_execution, with_additional_permissions_requires_approval_under_on_request, workspace_write_with_additional_permissions_can_write_outside_cwd);外部调用 3 个(assert_eq!, wait_for_event, panic!)。
wait_for_exec_approval_or_completion241–257 ↗
async fn wait_for_exec_approval_or_completion(
test: &TestCodex,
) -> Option<ExecApprovalRequestEvent>
作用:等待执行审批请求,但允许这一轮也可能直接完成。它适合那些“可能已经被预批准,也可能还需要审批”的场景。
数据流:输入测试环境 → 等待 ExecApprovalRequest 或 TurnComplete → 如果有审批就返回 Some(审批对象),如果直接完成就返回 None;其他事件会让测试失败。
调用关系:用于测试已申请权限是否能减少后续审批。调用方如果拿到审批,就继续模拟用户批准;如果没有,就说明系统已经直接放行或完成。
调用图:被 4 处调用(request_permissions_grants_apply_to_later_exec_command_calls, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_preapprove_explicit_exec_permissions_outside_on_request);外部调用 2 个(wait_for_event, panic!)。
expect_request_permissions_event259–279 ↗
async fn expect_request_permissions_event(
test: &TestCodex,
expected_call_id: &str,
) -> RequestPermissionProfile
作用:等待并确认系统发出了“模型正在申请权限”的事件。它还会核对 call_id,确保这次申请对应的是测试安排的那次工具调用。
数据流:输入测试环境和预期调用编号 → 等待 RequestPermissions 或 TurnComplete → 收到申请时检查 call_id 并返回申请的权限;若提前完成或事件不对,测试失败。
调用关系:独立 request_permissions 工具的测试都会用它拿到系统标准化后的权限请求,然后测试再提交用户的授权响应。
调用图:被 7 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, request_permissions_grants_apply_to_later_exec_command_calls, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_grants_do_not_carry_across_turns, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns);外部调用 3 个(assert_eq!, wait_for_event, panic!)。
workspace_write_excluding_tmp281–288 ↗
fn workspace_write_excluding_tmp() -> CorePermissionProfile
作用:创建一个“允许写工作区,但不要默认放开临时目录”的权限档案。这样测试能更清楚地区分:哪些写入是本来允许的,哪些必须靠额外授权。
数据流:不接收输入 → 调用 workspace_write_with,设置网络受限,并排除 TMPDIR 和 /tmp → 返回 CorePermissionProfile。
调用关系:许多测试用它作为基础权限,尤其是测试写工作区外目录、临时目录和跨轮授权时。
调用图:被 9 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, request_permissions_grants_apply_to_later_exec_command_calls, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_grants_do_not_carry_across_turns, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns, with_additional_permissions_denied_approval_blocks_execution, workspace_write_with_additional_permissions_can_write_outside_cwd);外部调用 1 个(workspace_write_with)。
requested_directory_write_permissions290–298 ↗
fn requested_directory_write_permissions(path: &Path) -> RequestPermissionProfile
作用:构造一个“请求写某个目录”的权限申请。它表示不额外请求读权限,只请求对指定目录的写权限。
数据流:输入目录路径 → 转成绝对路径 → 填入 FileSystemPermissions 的写根目录列表,读根目录为空列表 → 返回 RequestPermissionProfile。
调用关系:多个 request_permissions 工具测试用它来准备模型提出的原始权限请求。之后常和 normalized_directory_write_permissions 的结果对比。
调用图:调用 1 个内部函数(from_read_write_roots);被 7 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_grants_do_not_carry_across_turns, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns, request_permissions_tool_is_auto_denied_when_granular_request_permissions_is_disabled);外部调用 2 个(default, vec!)。
normalized_directory_write_permissions300–308 ↗
fn normalized_directory_write_permissions(path: &Path) -> Result<RequestPermissionProfile>
作用:构造一个标准化后的“请求写某个目录”的权限申请。标准化主要是把路径解析成系统真实路径,避免符号链接或相对写法造成误判。
数据流:输入目录路径 → 先 canonicalize 成真实绝对路径,再放进写权限列表 → 返回 RequestPermissionProfile;如果路径解析失败,会返回错误。
调用关系:测试经常用它作为期望值,去核对系统发出的权限申请是否已经把路径整理成统一形式。
调用图:调用 1 个内部函数(from_read_write_roots);被 6 处调用(partial_request_permissions_grants_do_not_preapprove_new_permissions, request_permissions_grants_apply_to_later_shell_command_calls, request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature, request_permissions_grants_do_not_carry_across_turns, request_permissions_preapprove_explicit_exec_permissions_outside_on_request, request_permissions_session_grants_carry_across_turns);外部调用 2 个(default, vec!)。
with_additional_permissions_requires_approval_under_on_request311–399 ↗
async fn with_additional_permissions_requires_approval_under_on_request() -> Result<()>
作用:验证在“按需审批”策略下,命令即使声明了额外权限,也必须先经过用户审批。没有这个保护,模型可能绕过用户直接写文件。
数据流:测试创建只读会话和一个待写目录 → 假服务器返回带额外权限的 shell_command → 提交用户回合,等待执行审批 → 模拟用户批准 → 检查命令成功并真的创建了文件。
调用关系:它串起 shell_event_with_request_permissions、submit_turn、expect_exec_approval、wait_for_completion 和 parse_result,完整演示一次“申请、审批、执行、验收”的流程。
调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, shell_event_with_request_permissions, submit_turn, wait_for_completion, from_read_write_roots);外部调用 9 个(read_only, default, assert!, assert_eq!, create_dir_all, remove_file, skip_if_no_network!, skip_if_sandbox!, vec!)。
request_permissions_tool_is_auto_denied_when_granular_request_permissions_is_disabled402–492 ↗
async fn request_permissions_tool_is_auto_denied_when_granular_request_permissions_is_disabled() -> Result<()>
作用:验证如果细粒度审批配置里关闭了 request_permissions,模型的独立权限申请会被自动拒绝,不会弹窗打扰用户。
数据流:测试设置 Granular 审批,但把 request_permissions 关掉 → 假服务器发出 request_permissions 工具调用 → 提交回合 → 确认没有 RequestPermissions 提示,只是正常结束 → 检查工具输出是空权限、按本轮范围、非严格自动审查。
调用关系:它使用 request_permissions_tool_event 和 requested_directory_write_permissions 构造申请,再通过事件等待确认系统没有走人工审批通道。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, request_permissions_tool_event, requested_directory_write_permissions, submit_turn);外部调用 10 个(read_only, Granular, assert!, assert_eq!, wait_for_event, create_dir_all, from_str, skip_if_no_network!, skip_if_sandbox!, vec!)。
relative_additional_permissions_resolve_against_tool_workdir503–621 ↗
async fn relative_additional_permissions_resolve_against_tool_workdir(
command_tool: AdditionalPermissionsCommandTool,
) -> Result<()>
作用:验证额外权限里的相对路径会按工具自己的工作目录来解释。比如工具在 nested 目录运行,请求写 “.” 应该表示 nested,而不是项目根目录。
数据流:测试创建 nested 目录 → 模拟 shell_command 或 exec_command 在 nested 下运行,并请求写相对路径 “.” → 提交回合并等待审批 → 检查审批里的权限变成 nested 的真实绝对路径 → 批准后确认文件写在 nested 里。
调用关系:它通过 test_case 同时覆盖 shell_command 和 exec_command 两种工具,使用 submit_turn、expect_exec_approval 和 parse_result 来完成验证。
调用图:调用 10 个内部函数(ev_function_call, mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, submit_turn, wait_for_completion, from_read_write_roots);外部调用 11 个(read_only, default, assert!, assert_eq!, create_dir_all, remove_file, json!, to_string, skip_if_no_network!, skip_if_sandbox! (+1 more))。
read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write625–724 ↗
async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd_write() -> Result<()>
作用:验证只读基础权限加上一个额外写权限时,不会顺手放开当前工作目录的其他文件。也就是说,批准 A 文件不等于批准整个目录。
数据流:测试请求写一个特定文件,却让命令去写另一个未请求的当前目录文件 → 系统要求审批并显示原始申请 → 批准后运行命令 → 检查命令失败,两个文件都没有被写入。
调用关系:这是 macOS 沙箱相关测试。它用 shell_event_with_request_permissions 发起带权限命令,用 expect_exec_approval 确认审批,再用 parse_result 验证写入被挡住。
调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, shell_event_with_request_permissions, submit_turn, wait_for_completion, from_read_write_roots);外部调用 9 个(read_only, default, assert!, assert_eq!, format!, remove_file, skip_if_no_network!, skip_if_sandbox!, vec!)。
read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write728–828 ↗
async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp_write() -> Result<()>
作用:验证请求某个工作区文件的写权限,不会意外放开临时目录写入。临时目录常被程序滥用,所以这里特别防止权限变宽。
数据流:测试请求写工作区里的一个文件,却让命令写临时目录文件 → 提交回合并批准显示的请求 → 命令执行后应失败 → 检查临时文件和请求文件都不存在。
调用关系:这是 macOS 专用测试,流程和当前目录写入防扩大测试相似,用来覆盖 /tmp 或临时目录的特殊风险。
调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, shell_event_with_request_permissions, submit_turn, wait_for_completion, from_read_write_roots);外部调用 10 个(read_only, default, assert!, assert_eq!, format!, remove_file, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
workspace_write_with_additional_permissions_can_write_outside_cwd831–937 ↗
async fn workspace_write_with_additional_permissions_can_write_outside_cwd() -> Result<()>
作用:验证在工作区写权限基础上,如果用户批准了额外目录,命令确实可以写到当前工作目录之外。它证明授权不是只会被拒绝,也能正确放行。
数据流:测试创建一个工作区外临时目录 → 模型请求写该目录并执行写文件命令 → 系统发出审批,审批里的路径应被标准化 → 用户批准 → 命令成功写入并读回内容。
调用关系:它使用 workspace_write_excluding_tmp 设基础权限,用 shell_event_with_request_permissions 造请求,用 expect_exec_approval 和 parse_result 验证完整结果。
调用图:调用 11 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, shell_event_with_request_permissions, submit_turn, wait_for_completion, workspace_write_excluding_tmp (+1 more));外部调用 9 个(default, assert!, assert_eq!, format!, remove_file, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
with_additional_permissions_denied_approval_blocks_execution940–1043 ↗
async fn with_additional_permissions_denied_approval_blocks_execution() -> Result<()>
作用:验证用户拒绝额外权限审批后,命令不能执行,也不能偷偷创建文件。这是权限系统最基本的安全承诺。
数据流:测试准备一个工作区外文件路径 → 模型请求写这个目录 → 系统弹出执行审批 → 测试提交 Denied → 等待完成 → 检查输出包含“被用户拒绝”,退出状态不是成功,目标文件不存在。
调用关系:它和审批通过类测试形成对照:同样通过 shell_event_with_request_permissions 和 expect_exec_approval 走到审批点,但提交的是拒绝决定。
调用图:调用 11 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, expect_exec_approval, parse_result, shell_event_with_request_permissions, submit_turn, wait_for_completion, workspace_write_excluding_tmp (+1 more));外部调用 9 个(default, assert!, assert_eq!, assert_ne!, format!, remove_file, skip_if_no_network!, tempdir, vec!)。
request_permissions_grants_apply_to_later_exec_command_calls1046–1169 ↗
async fn request_permissions_grants_apply_to_later_exec_command_calls() -> Result<()>
作用:验证模型先用 request_permissions 申请到的权限,可以在同一轮后续 exec_command 中使用。这样模型不用每条命令都重复申请同一份权限。
数据流:假服务器先发权限申请,再发 exec_command 写外部目录 → 测试批准本轮有效的权限 → 后续命令如果还需要审批就批准 → 最后解析输出,确认文件写入成功且内容正确。
调用关系:它串联 expect_request_permissions_event、wait_for_exec_approval_or_completion、wait_for_completion 和 parse_result,测试“本轮授权会粘住到后续执行”。
调用图:调用 10 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, parse_result, submit_turn, wait_for_completion, wait_for_exec_approval_or_completion, workspace_write_excluding_tmp, from_read_write_roots);外部调用 7 个(default, assert_eq!, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
request_permissions_preapprove_explicit_exec_permissions_outside_on_request1172–1291 ↗
async fn request_permissions_preapprove_explicit_exec_permissions_outside_on_request() -> Result<()>
作用:验证如果后续 exec_command 明确带的权限,已经被前面的 request_permissions 批准过,就应该可以被预批准或顺利通过。
数据流:测试先申请写外部目录权限并批准 → 后续 exec_command 再带同样的额外权限执行写文件 → 如果系统仍要求执行审批,测试批准它 → 检查命令输出和文件内容都正确。
调用关系:它使用 requested_directory_write_permissions 和 normalized_directory_write_permissions 对照原始请求与标准化请求,并通过 wait_for_exec_approval_or_completion 兼容是否还出现执行审批。
调用图:调用 11 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn, wait_for_completion, wait_for_exec_approval_or_completion (+1 more));外部调用 7 个(assert!, assert_eq!, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
request_permissions_grants_apply_to_later_shell_command_calls1294–1405 ↗
async fn request_permissions_grants_apply_to_later_shell_command_calls() -> Result<()>
作用:验证 request_permissions 批准的权限同样适用于后续 shell_command。它确保两种命令工具在授权规则上保持一致。
数据流:测试先让模型申请写外部目录 → 用户批准本轮权限 → 后续 shell_command 写同一目录文件 → 如需执行审批则批准 → 解析输出并检查文件内容。
调用关系:它和 exec_command 版本成对存在,使用 shell_command_event 生成后续命令,并通过 expect_request_permissions_event 确认申请内容。
调用图:调用 11 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn, wait_for_completion, wait_for_exec_approval_or_completion (+1 more));外部调用 7 个(assert!, assert_eq!, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature1408–1521 ↗
async fn request_permissions_grants_apply_to_later_shell_command_calls_without_inline_permission_feature() -> Result<()>
作用:验证即使没有开启“命令里内联申请额外权限”的功能,独立 request_permissions 工具批准的权限仍然能给后续 shell_command 用。
数据流:测试配置只开启 RequestPermissionsTool,不开启 ExecPermissionApprovals → 先批准外部目录写权限 → 后续 shell_command 写该目录 → 检查命令成功、输出正确、文件内容正确。
调用关系:它证明独立申请权限工具不依赖内联权限功能。流程上仍使用 expect_request_permissions_event、submit_turn 和 parse_result。
调用图:调用 11 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn, wait_for_completion, wait_for_exec_approval_or_completion (+1 more));外部调用 7 个(assert!, assert_eq!, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
partial_request_permissions_grants_do_not_preapprove_new_permissions1524–1688 ↗
async fn partial_request_permissions_grants_do_not_preapprove_new_permissions() -> Result<()>
作用:验证用户只批准了一部分权限时,未批准的那部分不能被自动当成已批准。否则模型可以借“大包申请”绕过用户选择。
数据流:模型先申请写两个外部目录 → 用户只批准第一个 → 后续 exec_command 请求写第二个目录 → 系统必须再发执行审批,并在审批里合并已批准和新请求的权限 → 批准后命令才成功写入第二个目录。
调用关系:这是权限合并和部分授权的重点测试。它同时使用 expect_request_permissions_event、expect_exec_approval、normalized_directory_write_permissions 和 parse_result。
调用图:调用 12 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_exec_approval, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn, wait_for_completion (+2 more));外部调用 9 个(default, default, assert!, assert_eq!, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
request_permissions_grants_do_not_carry_across_turns1691–1803 ↗
async fn request_permissions_grants_do_not_carry_across_turns() -> Result<()>
作用:验证默认的本轮权限不会带到下一轮对话。这样用户说“这次可以”时,不会变成“以后都可以”。
数据流:第一轮申请并批准外部目录写权限,范围设为 Turn → 第一轮结束 → 第二轮模型尝试用缺少 additional_permissions 的 exec_command → 测试检查输出提示缺少权限参数,而不是复用上一轮授权。
调用关系:它用两段 mount_sse_sequence 模拟两轮对话,并通过 wait_for_completion 明确第一轮已经结束,再验证第二轮没有继承本轮授权。
调用图:调用 9 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, normalized_directory_write_permissions, requested_directory_write_permissions, submit_turn, wait_for_completion, workspace_write_excluding_tmp);外部调用 6 个(assert!, assert_eq!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
request_permissions_session_grants_carry_across_turns1807–1942 ↗
async fn request_permissions_session_grants_carry_across_turns() -> Result<()>
作用:验证如果用户明确把权限批准为 Session 范围,也就是整个会话有效,那么后续轮次可以继续使用。它和“本轮授权不跨轮”形成对照。
数据流:第一轮申请外部目录写权限 → 用户以 Session 范围批准 → 第一轮结束 → 第二轮执行写同一目录的命令 → 如仍出现执行审批则批准 → 检查命令成功、输出和文件内容正确。
调用关系:这是 macOS 专用测试,使用 expect_request_permissions_event 确认申请,之后跨两轮用同一个测试会话验证 Session 授权会保留。
调用图:调用 10 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn, wait_for_completion, workspace_write_excluding_tmp);外部调用 7 个(assert_eq!, wait_for_event, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
core/tests/suite/request_permissions_tool.rs源码 ↗
这个测试文件围绕一个场景:模型想在工作区外写文件,正常来说沙箱会拦住它;但如果它先调用 request_permissions 工具向用户要权限,用户同意后,本轮对话里的后续命令或 apply_patch 修改就应该能成功。文件先搭一个假的服务器,假装模型按顺序发来“申请权限”“执行命令”或“打补丁”的事件;再启动测试版 Codex,把权限策略、功能开关和用户回复都塞进去;最后检查外部文件是否真的被创建或修改。这里还特别测了 strict_auto_review,也就是“更严格的自动复核”:即使用户给了临时权限,apply_patch 还会走一次守卫检查。整体像一次彩排:假模型提出要求,假用户批准,系统必须按规则放行,并留下正确结果。
absolute_path42–44 ↗
fn absolute_path(path: &Path) -> AbsolutePathBuf
作用:把普通路径转换成系统内部要求的“绝对路径”类型。测试里申请文件权限时,不能用含糊的相对路径,必须说清楚到底是哪一个目录。
数据流:进去的是一个 Path 路径 → 它调用转换函数,确认这个路径是绝对路径 → 出来的是 AbsolutePathBuf;如果路径不是绝对路径,测试会直接失败。
调用关系:它是构造权限数据时的小工具,被 requested_directory_write_permissions 间接用于把要申请的目录包装成权限系统能识别的格式。
调用图:调用 1 个内部函数(try_from)。
request_permissions_tool_event46–57 ↗
fn request_permissions_tool_event(
call_id: &str,
reason: &str,
permissions: &RequestPermissionProfile,
) -> Result<Value>
作用:制造一条假的模型工具调用事件,内容是“我想请求这些权限,理由是某某”。测试服务器会把这条事件发给 Codex,模拟真实模型调用 request_permissions 工具。
数据流:进去的是调用编号、申请理由、权限内容 → 它们被打包成 JSON(一种常见的结构化文本格式)字符串 → 出来的是一个函数调用事件,名字固定为 request_permissions。
调用关系:各个测试在安排假服务器返回内容时会用它。它把权限申请这一步伪装成模型真实发来的工具调用,后面 expect_request_permissions_event 会检查 Codex 是否正确把这件事通知出来。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
exec_command_event59–66 ↗
fn exec_command_event(call_id: &str, command: &str) -> Result<Value>
作用:制造一条假的模型工具调用事件,表示模型想执行一段 shell 命令。这里主要用来测试:申请权限被批准后,后续写文件命令能不能真的跑通。
数据流:进去的是调用编号和命令字符串 → 它把命令和一个等待时间字段做成 JSON 字符串 → 出来的是名为 exec_command 的函数调用事件。
调用关系:执行命令类测试会把它放进假服务器的事件序列里。权限批准之后,Codex 看到这条事件就会尝试执行命令,测试再用 parse_result 检查结果。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
build_add_file_patch68–74 ↗
fn build_add_file_patch(patch_path: &Path, content: &str) -> String
作用:生成一段“新增文件”的补丁文本。补丁可以理解成一张改文件的施工单,告诉系统要在哪个路径新建文件、写入什么内容。
数据流:进去的是目标文件路径和文件内容 → 它按 apply_patch 工具认识的格式拼出补丁文本 → 出来的是完整的补丁字符串。
调用关系:apply_patch_after_request_permissions 会调用它来准备一次工作区外的文件修改。之后这个补丁会被放进假的 apply_patch 工具调用里,验证临时授权是否能让补丁成功应用。
调用图:被 1 处调用(apply_patch_after_request_permissions);外部调用 1 个(format!)。
workspace_write_excluding_tmp76–83 ↗
fn workspace_write_excluding_tmp() -> PermissionProfile
作用:创建一个基础权限档案:允许写工作区,但不把临时目录算进去,网络也受限制。这样测试才能明确证明“工作区外的临时目录”必须靠额外申请才能写。
数据流:它不接收外部参数 → 调用权限构造函数,设置工作区写权限、受限网络,以及排除临时目录的选项 → 出来的是 PermissionProfile 权限档案。
调用关系:两个主要测试都会先用它搭出初始权限环境。它让后续 requested_directory_write_permissions 申请到的目录成为真正的额外授权,而不是一开始就被默认允许。
调用图:调用 1 个内部函数(workspace_write_with);被 2 处调用(apply_patch_after_request_permissions, approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args)。
requested_directory_write_permissions85–93 ↗
fn requested_directory_write_permissions(path: &Path) -> RequestPermissionProfile
作用:构造“我想写这个目录”的权限申请。它表示不额外给读目录,只把某个目录加入可写范围。
数据流:进去的是一个目录路径 → 路径被转成绝对路径,并放进“可写根目录”列表 → 出来的是 RequestPermissionProfile,也就是要发给用户审批的权限请求。
调用关系:执行命令测试和补丁测试都会调用它,作为模型 request_permissions 工具调用里的申请内容。随后 expect_request_permissions_event 会拿系统发出的申请来对比。
调用图:调用 1 个内部函数(from_read_write_roots);被 2 处调用(apply_patch_after_request_permissions, approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args);外部调用 2 个(default, vec!)。
normalized_directory_write_permissions95–103 ↗
fn normalized_directory_write_permissions(path: &Path) -> Result<RequestPermissionProfile>
作用:构造系统实际应该看到的标准化权限申请。标准化就是把路径解析成真实路径,避免符号链接、相对路径等造成“看起来一样其实不一样”的问题。
数据流:进去的是目录路径 → 它先 canonicalize,也就是让操作系统解析出规范真实路径,再转成绝对路径并放进可写列表 → 出来的是标准化后的 RequestPermissionProfile,过程可能因为路径不存在或解析失败而报错。
调用关系:两个主要测试都会用它做“正确答案”。模型申请原始目录后,Codex 应该把它规范化;测试用这个函数生成期望值,再和 expect_request_permissions_event 拿到的结果比较。
调用图:调用 1 个内部函数(from_read_write_roots);被 2 处调用(apply_patch_after_request_permissions, approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args);外部调用 2 个(default, vec!)。
parse_result105–133 ↗
fn parse_result(item: &Value) -> (Option<i64>, String)
作用:从工具调用输出里读出命令退出码和标准输出。退出码可以理解成程序跑完后的成绩单,0 通常表示成功。
数据流:进去的是一段 JSON 值,里面应有 output 字段 → 它先尝试按结构化 JSON 解析;如果不是 JSON,就用正则表达式(一种按模式找文字的工具)从普通文本里抠出退出码和输出 → 出来的是可选退出码和输出文本。
调用关系:执行命令测试和补丁测试都在最后调用它。前面的工具调用会产生输出,parse_result 把这些输出翻译成测试容易判断的形式,比如是否成功、是否包含预期文字。
调用图:被 2 处调用(apply_patch_after_request_permissions, approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args);外部调用 2 个(new, get)。
submit_turn135–173 ↗
async fn submit_turn(
test: &TestCodex,
prompt: &str,
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
approvals_reviewer: Option<ApprovalsReviewer>,
) -> Re
作用:向测试中的 Codex 提交一轮用户输入,并同时指定这一轮使用什么权限策略、沙箱策略和审批人。它相当于把“用户说了什么”和“这次对话有哪些安全规则”一起发车。
数据流:进去的是测试对象、用户提示词、审批策略、权限档案、可选审批复核人 → 它从测试配置里取当前模型和工作目录,把权限档案转换成这一轮需要的沙箱字段,再组装成 Op::UserInput 发给 Codex → 出来是异步提交结果;成功时系统开始处理这一轮对话。
调用关系:两个主要测试都会调用它来启动被测流程。它调用 local_selections 和 turn_permission_fields 准备环境与权限字段,然后把控制权交给 Codex 的 submit。
调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 2 处调用(apply_patch_after_request_permissions, approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args);外部调用 2 个(default, vec!)。
wait_for_completion175–180 ↗
async fn wait_for_completion(test: &TestCodex)
作用:等待当前测试对话结束。它让测试不要太早检查结果,必须等 Codex 发出“这一轮完成”的事件。
数据流:进去的是测试对象 → 它持续监听 Codex 事件,直到看到 TurnComplete → 不返回业务数据,只保证等待结束后这一轮已经完成。
调用关系:apply_patch_after_request_permissions 在 strict_auto_review 场景会用它。因为严格复核还会多跑一次守卫请求,所以测试要等整轮结束后再检查守卫请求是否出现。
调用图:被 1 处调用(apply_patch_after_request_permissions);外部调用 1 个(wait_for_event)。
expect_request_permissions_event182–202 ↗
async fn expect_request_permissions_event(
test: &TestCodex,
expected_call_id: &str,
) -> RequestPermissionProfile
作用:等待并确认系统真的发出了“请求权限”的事件,而且调用编号是预期的那个。它防止测试误把其他事件当成权限申请。
数据流:进去的是测试对象和期望的调用编号 → 它监听事件,允许看到 RequestPermissions 或 TurnComplete;如果先完成就报错,如果收到权限申请就核对 call_id → 出来的是系统标准化后的权限请求内容。
调用关系:两个主要测试都在 submit_turn 后调用它。它接住 request_permissions_tool_event 引发的系统事件,然后测试再用 RequestPermissionsResponse 模拟用户批准。
调用图:被 2 处调用(apply_patch_after_request_permissions, approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args);外部调用 3 个(assert_eq!, wait_for_event, panic!)。
approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args206–329 ↗
async fn approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args() -> Result<()>
作用:这是一个完整测试:用户批准写某个外部文件夹后,后面的 exec_command 写文件命令应该能成功,而且不需要再靠额外沙箱参数绕路。
数据流:它先跳过不适合的环境,启动假服务器和测试版 Codex,创建一个临时外部目录 → 假服务器依次返回权限申请、执行写文件命令、最终回复 → 测试提交用户请求,等待权限申请,模拟用户批准本轮写权限 → 之后等待命令执行完成,读取工具输出和真实文件内容 → 最后确认退出码成功、输出含有预期文字、文件确实被写入。
调用关系:它是本文件的主测试之一,串起 workspace_write_excluding_tmp、requested_directory_write_permissions、normalized_directory_write_permissions、submit_turn、expect_request_permissions_event 和 parse_result。它验证 request_permissions 的批准会影响后续 exec_command。
调用图:调用 9 个内部函数(mount_sse_sequence, start_mock_server, test_codex, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn, workspace_write_excluding_tmp);外部调用 8 个(assert!, assert_eq!, wait_for_event, format!, skip_if_no_network!, skip_if_sandbox!, tempdir, vec!)。
approved_folder_write_request_permissions_unblocks_later_apply_patch333–341 ↗
async fn approved_folder_write_request_permissions_unblocks_later_apply_patch() -> Result<()>
作用:这是 apply_patch 场景的入口测试:确认用户批准写外部文件夹后,后面的补丁修改可以成功。它分别跑普通审批和严格自动复核两种模式。
数据流:它先检查网络和沙箱环境是否适合跑测试 → 调用 apply_patch_after_request_permissions 跑一次普通模式,再跑一次 strict_auto_review 模式 → 两次都成功才算这个测试通过。
调用关系:它本身不搭细节,而是把实际工作交给 apply_patch_after_request_permissions。这样同一套流程可以用不同 strict_auto_review 参数复用。
调用图:调用 1 个内部函数(apply_patch_after_request_permissions);外部调用 2 个(skip_if_no_network!, skip_if_sandbox!)。
apply_patch_after_request_permissions343–514 ↗
async fn apply_patch_after_request_permissions(strict_auto_review: bool) -> Result<()>
作用:测试“先请求权限,再 apply_patch 修改外部文件”的完整流程。它还检查严格自动复核模式下,系统是否真的把补丁内容送去守卫检查。
数据流:进去的是 strict_auto_review 布尔值,表示是否开启严格自动复核 → 它启动假服务器和测试 Codex,创建外部临时目录,生成新增文件补丁,安排假服务器依次发出权限申请、apply_patch 调用、可选守卫回复、最终回复 → 测试提交用户请求,等待权限申请,模拟用户批准 → 普通模式下确认没有再冒出 apply_patch 审批;严格模式下等待完成并检查守卫请求包含文件名和补丁内容 → 最后解析补丁输出,确认成功,并读取真实文件验证内容。
调用关系:它由 approved_folder_write_request_permissions_unblocks_later_apply_patch 调用,是补丁测试的核心。它使用 build_add_file_patch 准备施工单,用 expect_request_permissions_event 接住权限申请,用 parse_result 判断 apply_patch 输出。
调用图:调用 12 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, build_add_file_patch, expect_request_permissions_event, normalized_directory_write_permissions, parse_result, requested_directory_write_permissions, submit_turn (+2 more));被 1 处调用(approved_folder_write_request_permissions_unblocks_later_apply_patch);外部调用 6 个(assert!, assert_eq!, wait_for_event, panic!, tempdir, vec!)。
用户输入与审查调解
这些测试跟踪需要系统询问用户输入,或将工作路由到审查和 Guardian 审查层的调解式交互路径。
core/tests/suite/request_user_input.rs源码 ↗
这个测试文件像是在模拟一场完整对话:先搭一个假的服务器,假装模型返回了一个“我要问用户问题”的工具调用;然后测试 Codex 有没有把这个问题变成前端能看到的事件;接着再模拟用户作答,确认答案会被包装好并发回模型。它还检查两个容易出错的边角情况:一是工具调用里带了自动处理时间 autoResolutionMs 时,系统要原样告诉前端;二是如果用户在等待回答时打断,本来被暂存的 token 统计(可以理解成这次模型用了多少“字数额度”)仍然要发出来。文件还验证不同协作模式下的权限:Plan 模式允许提问,Default 模式默认不允许但开功能开关后允许,Execute 和 Pair Programming 模式要拒绝。整体上,它保证“模型问人、人答模型”这条链路不会断,也不会在错误场景里乱问。
call_output39–50 ↗
fn call_output(req: &ResponsesRequest, call_id: &str) -> String
作用:这个小工具函数从模拟服务器收到的请求里,取出某个工具调用的返回内容。它顺便检查 call_id 对不对,避免测试误把别的工具调用结果当成目标结果。
数据流:进去的是一次发给模型的请求记录和目标 call_id → 它先找到这个 call_id 对应的 function_call_output,再确认里面写的 call_id 没串号,然后取出文本内容 → 出来的是这段工具输出文本;如果找不到或内容为空,测试会直接失败。
调用关系:它服务于 request_user_input_round_trip_for_mode。完整往返测试在用户回答后,会用它检查系统最终发回模型的答案 JSON 是否正确。
调用图:调用 2 个内部函数(function_call_output, function_call_output_content_and_success);被 1 处调用(request_user_input_round_trip_for_mode);外部调用 1 个(assert_eq!)。
call_output_content_and_success52–67 ↗
fn call_output_content_and_success(
req: &ResponsesRequest,
call_id: &str,
) -> (String, Option<bool>)
作用:这个小工具函数也从模拟请求里取工具调用输出,但它除了拿文本,还拿 success 标记。success 可以理解成“这个工具调用是否明确成功或失败”的状态。
数据流:进去的是请求记录和 call_id → 它找出对应的 function_call_output,确认 call_id 没错,再取出输出文字和 success 字段 → 出来是一组结果:输出文字,以及 success 是 true、false,还是根本没有写。
调用关系:它被 assert_request_user_input_rejected 使用。拒绝测试需要确认返回给模型的是“这个模式不可用”的说明,并且 success 没有被额外标成成功或失败。
调用图:调用 2 个内部函数(function_call_output, function_call_output_content_and_success);被 1 处调用(assert_request_user_input_rejected);外部调用 1 个(assert_eq!)。
request_user_input_round_trip_resolves_pending70–72 ↗
async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()>
作用:这是一个测试入口,检查最普通的成功场景:模型问用户,系统等待,用户回答后流程继续。它验证“等待回答的状态”能被正确解除。
数据流:进去没有外部输入 → 它指定使用 Plan 模式,并且不设置自动处理时间 → 结果是调用共享测试 request_user_input_round_trip_for_mode,完成一整套问答往返检查。
调用关系:它本身不做细节,像一个测试用例标签,把具体工作交给 request_user_input_round_trip_for_mode。
调用图:调用 1 个内部函数(request_user_input_round_trip_for_mode)。
request_user_input_round_trip_emits_auto_resolution_ms75–77 ↗
async fn request_user_input_round_trip_emits_auto_resolution_ms() -> anyhow::Result<()>
作用:这是一个测试入口,检查模型发来的 autoResolutionMs 会不会被系统传给前端。这个字段表示“如果用户一直不答,多久后可以自动处理”。
数据流:进去没有外部输入 → 它指定 Plan 模式,并传入 60000 毫秒 → 共享测试会确认前端收到的 RequestUserInput 事件里带着同样的时间值。
调用关系:它复用 request_user_input_round_trip_for_mode,只是多加了 auto_resolution_ms 这个测试条件。
调用图:调用 1 个内部函数(request_user_input_round_trip_for_mode)。
request_user_input_round_trip_for_mode79–229 ↗
async fn request_user_input_round_trip_for_mode(
mode: ModeKind,
auto_resolution_ms: Option<u64>,
) -> anyhow::Result<()>
作用:这是本文件最核心的成功路径测试。它模拟模型发起 request_user_input、前端收到问题、用户提交答案、系统再把答案发回模型的完整过程。
数据流:进去的是一个协作模式 mode,以及可选的 auto_resolution_ms → 它启动假服务器,配置测试版 Codex,准备模型返回的流式事件,提交一条用户消息,然后等待系统发出 RequestUserInput 事件;接着它确认 token 统计不会在问题未回答前提前出现,再模拟用户提交答案 → 出来是一次通过的测试:系统继续完成回合,第二次发给模型的请求里包含正确的答案 JSON;过程中会向测试 Codex 提交用户输入和用户答案。
调用关系:request_user_input_round_trip_resolves_pending、request_user_input_round_trip_emits_auto_resolution_ms 和 request_user_input_round_trip_in_default_mode_with_feature 都调用它。它会使用 start_mock_server 和 mount_sse_once 搭假模型服务,用 wait_for_event_match 等待系统事件,并用 call_output 检查最终发回模型的内容。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, call_output);被 3 处调用(request_user_input_round_trip_emits_auto_resolution_ms, request_user_input_round_trip_in_default_mode_with_feature, request_user_input_round_trip_resolves_pending);外部调用 10 个(default, new, assert!, assert_eq!, wait_for_event, wait_for_event_match, json!, from_str, skip_if_no_network!, vec!)。
ev_rate_limits231–249 ↗
fn ev_rate_limits() -> Value
作用:这个函数造出一段假的“速率限制”事件数据。速率限制可以理解成服务告诉客户端:你现在还能不能继续用、额度用了多少。
数据流:进去没有输入 → 它用固定字段拼出一段 JSON 数据,里面写着 plus 计划、未达到限制、使用了 42% 等信息 → 出来的是这段 JSON,供模拟的模型响应流使用。
调用关系:它被成功往返测试间接放进模拟服务器的事件流里,用来确保中途出现这种额外事件时,request_user_input 的等待和恢复逻辑仍然正常。
调用图:外部调用 1 个(json!)。
request_user_input_interrupt_emits_deferred_token_count252–339 ↗
async fn request_user_input_interrupt_emits_deferred_token_count() -> anyhow::Result<()>
作用:这个测试检查用户在等待回答时打断流程,系统是否仍然发出之前被暂存的 token 统计。token 统计就是这次模型调用消耗了多少额度的记录。
数据流:进去没有外部输入 → 它搭好假服务器,让模型先发 request_user_input,再在完成事件里带上 total_tokens=77;系统收到提问后,测试立刻提交 Interrupt 打断 → 出来要看到 TokenCount 事件里有 77,并且随后收到 TurnAborted,说明回合被正确中止但统计没有丢。
调用关系:这是一个独立测试。它和成功往返测试一样使用假服务器、测试 Codex、权限设置和事件等待工具,但它走的是“被打断”路线,而不是提交用户答案。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields);外部调用 7 个(default, assert_eq!, wait_for_event, wait_for_event_match, json!, skip_if_no_network!, vec!)。
assert_request_user_input_rejected341–424 ↗
async fn assert_request_user_input_rejected(mode_name: &str, build_mode: F) -> anyhow::Result<()>
作用:这个共享测试函数检查某些协作模式下,request_user_input 必须被拒绝。它避免模型在不适合追问用户的模式里弹出问题,扰乱执行流程。
数据流:进去的是模式名称 mode_name,以及一个能按模型名构造 CollaborationMode 的函数 build_mode → 它启动假服务器,让模型尝试调用 request_user_input,然后用指定模式提交用户输入 → 系统不应该向前端发问题,而是继续下一次模型请求,并把“该模式不可用”的文字作为工具输出发回;函数最后检查这段输出和 success 状态。
调用关系:request_user_input_rejected_in_execute_mode_alias、request_user_input_rejected_in_default_mode_by_default 和 request_user_input_rejected_in_pair_mode_alias 都调用它。它把三种拒绝场景的共同流程收拢在一起,并用 call_output_content_and_success 检查模型收到的拒绝说明。
调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, call_output_content_and_success);被 3 处调用(request_user_input_rejected_in_default_mode_by_default, request_user_input_rejected_in_execute_mode_alias, request_user_input_rejected_in_pair_mode_alias);外部调用 7 个(default, assert_eq!, wait_for_event, format!, json!, skip_if_no_network!, vec!)。
request_user_input_rejected_in_execute_mode_alias427–437 ↗
async fn request_user_input_rejected_in_execute_mode_alias() -> anyhow::Result<()>
作用:这个测试确认 Execute 模式下不能使用 request_user_input。Execute 模式更像“直接执行任务”,不应该中途让模型随便向用户追问。
数据流:进去没有外部输入 → 它构造一个 Execute 类型的 CollaborationMode → 交给 assert_request_user_input_rejected 去跑完整拒绝流程,结果应是模型收到“Execute 模式不可用”的输出。
调用关系:它是一个具体测试用例,本身只指定模式,实际搭服务器、提交消息、检查输出都交给 assert_request_user_input_rejected。
调用图:调用 1 个内部函数(assert_request_user_input_rejected)。
request_user_input_rejected_in_default_mode_by_default440–450 ↗
async fn request_user_input_rejected_in_default_mode_by_default() -> anyhow::Result<()>
作用:这个测试确认 Default 模式默认不能使用 request_user_input。也就是说,普通默认模式下不会自动打开“模型向用户追问”的能力。
数据流:进去没有外部输入 → 它构造 Default 类型的 CollaborationMode,且不打开额外功能开关 → 交给 assert_request_user_input_rejected 检查,最后应看到“Default 模式不可用”的工具输出。
调用关系:它和 Default 模式允许提问的测试形成对照:不开功能开关时调用 assert_request_user_input_rejected,开了功能开关时走 request_user_input_round_trip_for_mode。
调用图:调用 1 个内部函数(assert_request_user_input_rejected)。
request_user_input_round_trip_in_default_mode_with_feature453–455 ↗
async fn request_user_input_round_trip_in_default_mode_with_feature() -> anyhow::Result<()>
作用:这个测试确认 Default 模式在打开对应功能开关后,可以正常使用 request_user_input。它保证新功能可以被开关控制,而不是永远禁用。
数据流:进去没有外部输入 → 它指定 Default 模式,并且不传自动处理时间 → 共享成功测试会在构建配置时打开 DefaultModeRequestUserInput 功能,然后验证完整问答往返成功。
调用关系:它调用 request_user_input_round_trip_for_mode。该共享函数内部会看到 mode 是 Default,于是打开功能开关,再跑完整链路。
调用图:调用 1 个内部函数(request_user_input_round_trip_for_mode)。
request_user_input_rejected_in_pair_mode_alias458–468 ↗
async fn request_user_input_rejected_in_pair_mode_alias() -> anyhow::Result<()>
作用:这个测试确认 Pair Programming 模式下不能使用 request_user_input。Pair Programming 可以理解成结对编程模式,这里要求模型不要通过这个工具另开一套追问流程。
数据流:进去没有外部输入 → 它构造 PairProgramming 类型的 CollaborationMode → 交给 assert_request_user_input_rejected 跑完整流程,结果应是工具输出说明“Pair Programming 模式不可用”。
调用关系:它是拒绝类测试之一,和 Execute、Default 默认拒绝共用 assert_request_user_input_rejected,避免三份测试重复写同样的服务器和断言代码。
调用图:调用 1 个内部函数(assert_request_user_input_rejected)。
core/tests/suite/review.rs源码 ↗
这里不是产品代码,而是一套自动化测试。它会搭一个假的 Responses API 服务器,也就是假装成模型服务,按测试需要返回 SSE(服务器持续推送事件,像直播字幕一样一段段传回来)。然后它创建一个临时的 Codex 会话,提交 Op::Review 审查请求,观察系统发出的事件顺序、审查结果解析、请求内容、历史文件记录和模型选择。重点覆盖几类容易出错的地方:结构化 JSON 能不能变成审查结果;普通文本能不能兜底显示;审查时不要把普通聊天的流式消息乱抛给界面;审查会话既要和父会话隔离,又要把结果回写到父会话;以及基于分支审查时要用当前工作目录。辅助函数负责造假的模型事件、启动假服务器、创建或恢复测试会话。
review_op_emits_lifecycle_and_review_output41–201 ↗
async fn review_op_emits_lifecycle_and_review_output()
作用:这个测试确认一次正常的审查请求会完整走完生命周期:进入审查、带着审查结果退出审查、最后回合完成。它还检查模型返回的结构化 JSON 有没有被准确变成内部的审查结果。
数据流:进去的是一段假的模型返回内容,内容是 JSON 格式的审查发现;测试先用 assistant_message_sse 包成模型推送事件,再用 start_responses_server_with_sse 放到假服务器上,然后用 new_conversation_for_server 建会话并提交审查请求;出来的是系统事件、请求日志和 rollout 历史文件,测试逐项核对结果结构、请求头、父会话编号以及历史里保存的用户摘要和助手审查文本。
调用关系:它是审查功能最完整的一条端到端测试,会调用造事件、起假服务器、建会话这些辅助函数;中间还用 render_review_output_text 对照历史中应保存的可读审查文本,最后依靠等待事件的测试工具确认流程顺序。
调用图:调用 4 个内部函数(render_review_output_text, assistant_message_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 12 个(new, new, assert!, assert_eq!, wait_for_event, panic!, from_str, from_value, json!, skip_if_no_network! (+2 more))。
review_op_with_plain_text_emits_review_fallback209–251 ↗
async fn review_op_with_plain_text_emits_review_fallback()
作用:这个测试确认模型没有返回 JSON、只返回普通文字时,审查功能也不会崩。系统应该把这段文字放进审查结果的整体说明里,让用户仍然能看到答案。
数据流:进去的是假服务器返回的 just plain text;测试创建会话、提交审查请求,然后等进入审查、退出审查和回合完成事件;出来的是一个兜底的 ReviewOutputEvent,其中没有具体发现,但 overall_explanation 保存了原始文字。
调用关系:它复用 assistant_message_sse、start_responses_server_with_sse 和 new_conversation_for_server 搭测试环境,专门补上正常 JSON 之外的异常输入场景,保证审查子流程能优雅收尾。
调用图:调用 3 个内部函数(assistant_message_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 7 个(new, default, new, assert_eq!, wait_for_event, panic!, skip_if_no_network!)。
review_does_not_emit_agent_message_on_structured_output321–389 ↗
async fn review_does_not_emit_agent_message_on_structured_output()
作用:这个测试确认模型返回结构化审查 JSON 时,界面只收到一个最终助手消息,而不是一堆流式消息。这样审查结果的展示路径更干净,也避免重复显示。
数据流:进去的是一段包含发现、总体评价和置信度的假 JSON;测试把它作为助手消息放进假服务器,提交审查请求后收集事件直到完成;出来的检查结果是:进入审查和退出审查都出现,并且 AgentMessage 事件数量正好是一个。
调用关系:它和过滤流式事件的测试互相补充:前者看不该出现的增量事件,这里看最终助手消息数量;它复用 assistant_message_sse、start_responses_server_with_sse 和 new_conversation_for_server。
调用图:调用 3 个内部函数(assistant_message_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 7 个(new, new, assert!, assert_eq!, wait_for_event, json!, skip_if_no_network!)。
review_uses_custom_review_model_from_config394–440 ↗
async fn review_uses_custom_review_model_from_config()
作用:这个测试确认配置里单独设置了 review_model 时,审查请求会使用这个专用模型,而不是普通聊天模型。这样用户可以让审查任务走更适合审代码的模型。
数据流:进去的是一份测试配置:普通模型设为 gpt-4.1,审查模型设为 gpt-5.4;测试提交审查请求并等流程完成;出来的是假服务器记录到的请求体,测试读取其中的 model 字段,确认它等于 gpt-5.4。
调用关系:它用 completed_sse 准备一个只表示完成的假响应,用 new_conversation_for_server 在创建会话时改配置;它验证的是配置选择逻辑在审查流程里的实际效果。
调用图:调用 3 个内部函数(completed_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 5 个(new, new, assert_eq!, wait_for_event, skip_if_no_network!)。
review_uses_session_model_when_review_model_unset445–488 ↗
async fn review_uses_session_model_when_review_model_unset()
作用:这个测试确认没有单独配置 review_model 时,审查请求会退回使用当前会话的模型。这样默认行为清楚,不会莫名其妙换模型。
数据流:进去的是一份只有普通模型 gpt-4.1、没有审查模型的配置;测试提交审查请求、等待完成,再查看假服务器收到的请求体;出来的断言是 model 字段正好是 gpt-4.1。
调用关系:它和自定义审查模型测试是一对:一个验证有专用模型时优先用它,一个验证没有时用会话模型;同样依赖 completed_sse、start_responses_server_with_sse 和 new_conversation_for_server。
调用图:调用 3 个内部函数(completed_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 5 个(new, new, assert_eq!, wait_for_event, skip_if_no_network!)。
review_input_isolated_from_parent_history496–667 ↗
async fn review_input_isolated_from_parent_history()
作用:这个测试确认审查会话开始时不会把父聊天里的旧对话塞进模型输入。审查应该像开了一个临时小房间,只带环境信息和本次审查提示,避免旧聊天污染判断。
数据流:进去的是一个手写的历史文件,里面有父会话的用户消息和助手回复;测试用 resume_conversation_for_server 从这个文件恢复会话,再提交审查请求;出来会检查模型请求的 input:里面应有环境上下文和本次审查文本,但不应把旧对话当作审查输入,同时 instructions 应等于审查专用提示 REVIEW_PROMPT,rollout 里还应记录一条用户发起审查但被中断的说明。
调用关系:它是少数使用恢复会话辅助函数的测试,专门模拟真实用户带历史记录继续使用的情况;它调用 completed_sse 和 start_responses_server_with_sse 控制模型端只完成不输出,重点检查请求内容和历史记录边界。
调用图:调用 3 个内部函数(completed_sse, resume_conversation_for_server, start_responses_server_with_sse);外部调用 15 个(new, new, new_v4, assert!, assert_eq!, wait_for_event, format!, from_str, from_value, json! (+5 more))。
review_history_surfaces_in_parent_session672–765 ↗
async fn review_history_surfaces_in_parent_session()
作用:这个测试确认审查结束后,审查过程和结果会出现在父会话后续输入里。这样用户回到普通聊天后,模型还能参考刚才的审查结论。
数据流:进去的是假服务器两次响应:第一次给审查输出,第二次给后续普通聊天用;测试先提交审查,再提交一条普通用户输入;出来会检查第二次请求的输入,最后一条应是新的用户消息,同时也应包含“用户发起审查”的记录和审查助手输出。
调用关系:它先通过 assistant_message_sse 模拟审查结果,再用 new_conversation_for_server 建普通会话;它连接了审查子会话和父会话后续回合,验证两者不是完全断开的。
调用图:调用 3 个内部函数(assistant_message_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 8 个(new, default, new, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
review_uses_overridden_cwd_for_base_branch_merge_base770–867 ↗
async fn review_uses_overridden_cwd_for_base_branch_merge_base()
作用:这个测试确认基于分支的审查会使用运行时覆盖后的当前工作目录,而不是会话最初的目录。否则系统可能在错误的 Git 仓库里算差异,审错代码。
数据流:进去的是一个临时 Git 仓库,测试初始化 main 分支、提交文件并拿到当前提交的 SHA;会话一开始的目录故意设到别处,随后通过线程设置把环境切到这个仓库,再提交基于 main 的审查;出来会检查模型请求输入里包含正确的 merge-base 提交 SHA,说明它确实在覆盖后的目录里计算。
调用关系:它调用 completed_sse 准备空完成响应,用 new_conversation_for_server 建会话,并通过 local_selections 提交运行时环境覆盖;它覆盖的是审查提示生成和 Git 工作目录选择之间的衔接。
调用图:调用 4 个内部函数(local_selections, completed_sse, new_conversation_for_server, start_responses_server_with_sse);外部调用 11 个(new, default, from_utf8, new, assert!, assert_eq!, new, submit_thread_settings, wait_for_event, skip_if_no_network! (+1 more))。
assistant_message_sse869–874 ↗
fn assistant_message_sse(text: &str) -> Vec<serde_json::Value>
作用:这个小工具把一段助手文字包装成假模型会推送的事件列表。测试用它快速模拟“模型说了一段话,然后结束”。
数据流:进去的是一段文本;函数把它放进一个助手消息事件,再追加一个完成事件;出来的是一个事件数组,可以直接交给假 Responses API 服务器播放。
调用关系:它被多个审查结果相关测试调用,包括结构化 JSON、普通文本和父会话历史测试;它把重复的假响应拼装工作藏起来,让每个测试只关心模型要说什么。
调用图:被 4 处调用(review_does_not_emit_agent_message_on_structured_output, review_history_surfaces_in_parent_session, review_op_emits_lifecycle_and_review_output, review_op_with_plain_text_emits_review_fallback);外部调用 1 个(vec!)。
completed_sse876–878 ↗
fn completed_sse() -> Vec<serde_json::Value>
作用:这个小工具生成一个“模型请求已完成但没有内容”的假事件列表。适合那些只想检查请求内容、不关心模型回答的测试。
数据流:进去不需要参数;函数创建一个完成事件数组;出来的是可以挂到假服务器上的 SSE 事件列表。
调用关系:它被模型选择、输入隔离和工作目录相关测试使用;这些测试不需要审查文本,只需要让流程正常结束,然后检查系统发给模型的请求。
调用图:被 4 处调用(review_input_isolated_from_parent_history, review_uses_custom_review_model_from_config, review_uses_overridden_cwd_for_base_branch_merge_base, review_uses_session_model_when_review_model_unset);外部调用 1 个(vec!)。
start_responses_server_with_sse881–890 ↗
async fn start_responses_server_with_sse(
events: Vec<serde_json::Value>,
expected_requests: usize,
) -> (MockServer, ResponseMock)
作用:这个辅助函数启动一个假的 Responses API 服务器,并让它按顺序返回指定的 SSE 事件。它让测试不必真的访问外部网络或真实模型。
数据流:进去的是一组事件和预期请求次数;函数先启动 mock server,把事件包装成 SSE 响应,再按请求次数挂载到服务器;出来的是服务器对象和请求日志,测试可以用服务器跑流程,也可以用日志检查发出了什么请求。
调用关系:几乎所有测试都会先调用它搭建假模型服务;它内部把活交给 start_mock_server、responses::sse 和 mount_sse_sequence,是测试和假网络层之间的连接点。
调用图:调用 3 个内部函数(mount_sse_sequence, sse, start_mock_server);被 9 处调用(review_does_not_emit_agent_message_on_structured_output, review_filters_agent_message_related_events, review_history_surfaces_in_parent_session, review_input_isolated_from_parent_history, review_op_emits_lifecycle_and_review_output, review_op_with_plain_text_emits_review_fallback, review_uses_custom_review_model_from_config, review_uses_overridden_cwd_for_base_branch_merge_base, review_uses_session_model_when_review_model_unset);外部调用 1 个(vec!)。
new_conversation_for_server893–913 ↗
async fn new_conversation_for_server(
server: &MockServer,
codex_home: Arc<TempDir>,
mutator: F,
) -> Arc<CodexThread>
作用:这个辅助函数创建一个新的 Codex 测试会话,并把它配置成访问刚启动的假服务器。测试可以顺手传入一个配置修改函数,改变模型、目录等设置。
数据流:进去的是假服务器、临时 home 目录和一个配置修改回调;函数把服务器地址拼成 API base URL,交给测试会话构建器,并应用额外配置;出来的是可提交操作的 CodexThread,也就是测试里的会话对象。
调用关系:多数测试都靠它开一个干净的新会话;它调用 test_codex 创建测试构建器,之后各测试再向返回的会话提交审查或普通用户输入。
调用图:调用 1 个内部函数(test_codex);被 8 处调用(review_does_not_emit_agent_message_on_structured_output, review_filters_agent_message_related_events, review_history_surfaces_in_parent_session, review_op_emits_lifecycle_and_review_output, review_op_with_plain_text_emits_review_fallback, review_uses_custom_review_model_from_config, review_uses_overridden_cwd_for_base_branch_merge_base, review_uses_session_model_when_review_model_unset);外部调用 1 个(format!)。
resume_conversation_for_server916–937 ↗
async fn resume_conversation_for_server(
server: &MockServer,
codex_home: Arc<TempDir>,
resume_path: std::path::PathBuf,
mutator: F,
) -> Arc<CodexThread>
作用:这个辅助函数从已有的 rollout 历史文件恢复一个 Codex 测试会话,同时也把网络指向假服务器。它用来模拟用户接着旧会话继续操作。
数据流:进去的是假服务器、临时 home 目录、要恢复的历史文件路径和配置修改回调;函数设置 API 地址和测试 home,然后调用恢复流程读取历史;出来的是恢复后的 CodexThread,里面带着之前写入的会话记录。
调用关系:当前文件里主要由 review_input_isolated_from_parent_history 使用,因为那个测试需要先造一段父会话历史,再验证审查输入不会被旧历史污染;它和 new_conversation_for_server 作用相近,但走的是恢复旧会话路线。
调用图:调用 1 个内部函数(test_codex);被 1 处调用(review_input_isolated_from_parent_history);外部调用 1 个(format!)。
core/tests/suite/guardian_review.rs源码 ↗
这份测试只在非 Windows 系统上跑,因为它会创建并执行一个 Unix shell 脚本。测试先搭一个假的服务器,假装模型会依次要求执行命令、进行 Guardian 审查、最后回复 done。Guardian 可以理解成一道自动安全检查关:当命令需要更高权限时,系统先让它判断能不能放行。测试还配置了一个旧版 notify 通知脚本,这个脚本会把收到的通知内容写进文件。接着,测试让 Codex 执行一个需要 Guardian 审批的写文件命令。最后它检查三件事:Guardian 审查请求确实发出去了;命令真的被允许并写出了目标文件;通知文件里只有用户原始输入和最后助手回复,没有夹带 Guardian 审查时的内部说明文字。换句话说,这个文件是在防止“内部审批小会议的记录”被误塞进“发给外面的通知单”。
guardian_review_session_does_not_inherit_legacy_notify34–170 ↗
async fn guardian_review_session_does_not_inherit_legacy_notify() -> Result<()>
作用:这条测试验证:当一次需要 Guardian 自动审查的命令执行完后,旧版 notify 通知只收到正常会话内容,不会收到 Guardian 审查用的内部对话。有人改审批、沙箱或通知逻辑时,可以靠它及时发现泄露问题。
数据流:进去的是一套临时测试环境:假的模型服务器、临时目录、一个会把通知 JSON 写到文件里的 shell 脚本、审批策略和沙箱策略。函数先配置 Codex,让它启用旧版通知、按需审批、工作区写入沙箱,并打开 Guardian 自动审查;再给假服务器安排三段响应:父会话要求执行命令、Guardian 返回 allow、父会话最终说 done。然后它提交一条用户输入,让 Codex 去执行需要提权的命令。执行完成后,它读取假服务器收到的请求、通知脚本写出的文件、以及命令生成的输出文件。出来的结果是:如果 Guardian 请求存在、通知内容干净、输出文件内容正确,测试通过;如果内部审查文本混进通知或命令没执行成功,测试失败。
调用关系:这个函数是本文件唯一的测试入口,由 Tokio 异步测试框架在测试运行时自动调用。它用 start_mock_server 搭出假服务器,用 mount_sse_sequence 安排服务器返回的模型事件,用 test_codex 创建一套可控的 Codex 测试实例,用 local_selections 指定本地运行环境。提交用户输入后,它等待 TurnComplete 事件表示这一轮结束,然后自己完成所有断言检查。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, local_selections, test_codex);外部调用 18 个(default, from_millis, from_secs, new, assert!, assert_eq!, from_mode, wait_for_event, format!, set_permissions (+8 more))。
core/tests/suite/mcp_turn_metadata.rs源码 ↗
这份测试文件像是在给“助手调用外部应用”这条路做验收。这里的外部应用是日历工具,MCP 可以理解成一种让助手调用外部工具的协议。测试会搭一个假的服务器,假装模型先要求调用工具、再返回结果;也会搭一个假的应用服务器,记录真正发给日历工具的内容。文件里有两个小帮手用来改配置:把日历工具设成自动通过、需要提示、或预先批准;还可以指定审批人是用户,还是 Guardian 自动审查器(一个替用户判断风险的审查流程)。另一个帮手负责提交一次用户输入,把权限、沙盒和协作模式一起塞进去。三个测试分别验证:用户手动批准后,工具调用的元信息会标记“本轮曾请求用户输入”;默认自动审查配置确实会把审批送去 Guardian,而不是弹给用户;以及模型先调用 request_user_input 向用户提问,再调用日历工具时,也会记录这个事实。没有这些测试,系统可能会漏记关键上下文,导致后续安全判断或审计记录不准。
set_calendar_approval_mode44–61 ↗
fn set_calendar_approval_mode(config: &mut Config, approval_mode: AppToolApproval)
作用:这个函数给测试用的配置临时写入一段“日历应用的默认审批方式”。有人会用它来快速切换日历工具是自动执行、弹窗询问,还是直接批准。
数据流:进去的是一个可修改的配置对象,以及一个审批模式枚举值。函数先把枚举值翻译成配置文件里用的文字,比如 auto、prompt、approve;再拼出一小段 TOML 配置文本,TOML 是一种常见的配置文件格式;然后把它解析成配置层,塞回测试配置里。出来没有单独返回值,但传入的配置已经被改好,后面的测试会按这个审批方式运行。
调用关系:它是测试前的布置工具。比如 mcp_tool_call_metadata_records_prior_request_user_input_tool 会通过构建器调用它,把日历工具设成 Approve,避免测试重点被额外审批打断。它内部只做字符串拼接和 TOML 解析,不负责真正运行测试。
set_calendar_approval_mode_and_default_reviewer63–87 ↗
fn set_calendar_approval_mode_and_default_reviewer(
config: &mut Config,
approval_mode: AppToolApproval,
default_approvals_reviewer: ApprovalsReviewer,
)
作用:这个函数同时设置两件事:日历工具的审批方式,以及应用默认由谁来审查审批请求。它用来测试“配置优先级和审批路由”是否真的生效。
数据流:进去的是可修改配置、日历工具审批模式、默认审批人。函数把审批模式转成配置文字,再生成一段 TOML:一段写 apps._default 的 approvals_reviewer,表示应用默认审批人;另一段写 apps.calendar 的默认工具审批方式。解析成功后,它把这份用户配置压到配置层里。结果是配置对象被更新,测试里的系统会按这套规则决定审批请求发给用户还是自动审查器。
调用关系:它被需要检查审批路线的测试间接使用。approved_mcp_tool_call_metadata_records_prior_user_input_request 用它把默认审批人设成用户;apps_default_auto_review_routes_actual_mcp_approval_to_guardian 用它把默认审批人设成自动审查器。这样测试能证明路线来自 apps._default,而不是全局默认值。
submit_user_turn89–127 ↗
async fn submit_user_turn(
test: &TestCodex,
text: &str,
approval_policy: AskForApproval,
collaboration_mode: Option<CollaborationMode>,
) -> Result<()>
作用:这个函数替测试提交一次用户发言,并顺手带上本轮需要的权限、沙盒和协作模式设置。它把一堆重复的“开一轮对话”步骤包装起来,避免每个测试都写一遍。
数据流:进去的是测试环境、用户文字、审批策略,以及可选的协作模式。函数先拿到当前会话的模型名,再计算本轮权限字段:沙盒策略和权限档案,沙盒可以理解成给程序活动划边界的安全盒子。接着它向测试里的 Codex 实例提交 Op::UserInput,也就是“一次用户输入操作”,里面包含用户文本、环境选择、审批策略、沙盒策略、权限信息和协作模式。如果调用者没给协作模式,它会补一个默认模式。最后返回成功或错误,不直接产生用户可见输出。
调用关系:它是三个测试共同使用的启动按钮。approved_mcp_tool_call_metadata_records_prior_user_input_request、apps_default_auto_review_routes_actual_mcp_approval_to_guardian 和 mcp_tool_call_metadata_records_prior_request_user_input_tool 都先搭好服务器和配置,再调用它把用户问题送进系统。它会调用 local_selections 和 turn_permission_fields 准备环境与权限,然后把操作交给 test.codex.submit。
调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 3 处调用(approved_mcp_tool_call_metadata_records_prior_user_input_request, apps_default_auto_review_routes_actual_mcp_approval_to_guardian, mcp_tool_call_metadata_records_prior_request_user_input_tool);外部调用 2 个(default, vec!)。
approved_mcp_tool_call_metadata_records_prior_user_input_request130–231 ↗
async fn approved_mcp_tool_call_metadata_records_prior_user_input_request() -> Result<()>
作用:这个测试确认:当日历工具调用需要用户批准,并且用户批准了之后,真正发给应用服务器的工具调用会记录“本轮曾经请求过用户输入”。这对审计和后续安全判断很重要。
数据流:测试开始先跳过无网络环境,然后启动假模型服务器和假应用服务器。它安排模型的两段返回:第一段要求调用日历创建事件工具,第二段说完成。接着配置系统:日历工具需要 prompt,也就是要询问;默认应用审批人设成用户;同时启用工具调用的 MCP 询问功能。然后它提交用户请求,等待工具调用开始事件,再等待系统发出 ElicitationRequest,也就是“向用户征求批准”的请求。测试模拟用户接受审批,等本轮结束后,从假应用服务器取回那次日历工具调用记录,检查其中 _meta 里的 x-codex-turn-metadata/user_input_requested_during_turn 是否为 true。结果如果不是 true,测试就失败。
调用关系:这是一个完整端到端测试:它用 start_mock_server 和 mount_sse_sequence 假装模型输出,用 AppsTestServer 假装日历应用,用 submit_user_turn 开始一轮对话,用 wait_for_event 等系统事件,最后用 recorded_apps_tool_call_by_call_id 检查工具调用内容。它主要验证用户审批路径下的元信息记录。
调用图:调用 6 个内部函数(mount, recorded_apps_tool_call_by_call_id, search_capable_apps_builder, mount_sse_sequence, start_mock_server, submit_user_turn);外部调用 8 个(assert_eq!, wait_for_event, json!, panic!, to_string, skip_if_no_network!, unreachable!, vec!)。
apps_default_auto_review_routes_actual_mcp_approval_to_guardian234–335 ↗
async fn apps_default_auto_review_routes_actual_mcp_approval_to_guardian() -> Result<()>
作用:这个测试确认:如果应用默认审批人配置成自动审查器,真实的 MCP 工具审批会送去 Guardian,而不是弹给用户。它防止系统忽略 apps._default 配置,走错审批路线。
数据流:测试先启动假服务器,并准备三段模型响应:第一段请求创建日历事件;第二段模拟 Guardian 审查返回允许;第三段父流程返回 done。配置时,它故意把全局审批人设成用户,但把 apps._default 设成 AutoReview,并把日历工具设成需要 prompt。这样如果最后走了 Guardian,就说明应用默认配置真的生效。提交用户请求后,测试等待审批路线的结果:如果出现 ElicitationRequest,就代表错误地问了用户;它期望直接等到 TurnComplete。随后它在模型请求记录里找 Guardian 审查请求,检查审查提示词确实出现,并且请求内容包含 calendar_create_event 和 Lunch。最后它确认日历工具调用参数里真的有标题 Lunch。
调用关系:它和上一个测试很像,但重点从“元信息是否记录”转到“审批交给谁”。它调用 submit_user_turn 发起对话,用挂载好的 SSE 响应驱动假模型流程,再检查 responses.requests 里有没有 Guardian 请求,同时用 recorded_apps_tool_call_by_call_id 确认工具最终被调用。
调用图:调用 6 个内部函数(mount, recorded_apps_tool_call_by_call_id, search_capable_apps_builder, mount_sse_sequence, start_mock_server, submit_user_turn);外部调用 7 个(assert!, assert_eq!, wait_for_event, json!, to_string, skip_if_no_network!, vec!)。
mcp_tool_call_metadata_records_prior_request_user_input_tool338–461 ↗
async fn mcp_tool_call_metadata_records_prior_request_user_input_tool() -> Result<()>
作用:这个测试确认:如果模型在同一轮里先调用 request_user_input 工具向用户提问,之后再调用日历 MCP 工具,那么日历工具调用也会标记“本轮曾请求用户输入”。这覆盖的是另一种用户输入来源,不是审批弹窗,而是模型主动提问。
数据流:测试先准备假模型的三段响应:第一段调用 request_user_input,向用户问是否继续;第二段调用日历创建事件工具;第三段返回完成。它把日历工具配置成 Approve,也就是工具调用本身不再额外询问审批,避免混淆测试目标。然后提交用户输入,并把协作模式设为 Plan,意思是偏计划讨论的模式。系统发出 RequestUserInput 事件后,测试模拟用户选择“Yes (Recommended)”。接着它等待日历工具调用开始,再等整轮结束。最后从假应用服务器取回日历调用,检查元信息里的 user_input_requested_during_turn 是否为 true。
调用关系:它测试的是 request_user_input 这条路径。它先通过 wait_for_event_match 抓到用户输入请求,再用 test.codex.submit 提交 Op::UserInputAnswer 作为用户答案;之后等待 MCP 工具调用和回合结束。最后同样借助 recorded_apps_tool_call_by_call_id 检查发给应用服务器的真实请求。
调用图:调用 6 个内部函数(mount, recorded_apps_tool_call_by_call_id, search_capable_apps_builder, mount_sse_sequence, start_mock_server, submit_user_turn);外部调用 9 个(from, assert_eq!, wait_for_event, wait_for_event_match, json!, to_string, skip_if_no_network!, unreachable!, vec!)。
钩子拦截与通知
这些套件验证提示和工具执行周围的生命周期钩子,包括 MCP 特定拦截和最终面向用户的通知交付。
core/tests/suite/hooks.rs源码 ↗
这个测试文件模拟了很多真实场景:用户刚发消息、会话刚开始、模型要运行命令、命令运行完、需要用户批准权限、会话恢复或压缩时,外部钩子是否会按预期工作。它会临时写出 hooks.json、config.toml 和 Python 脚本,让测试里的假 Codex 去调用它们;同时用假的模型服务器返回预设响应。测试重点不是算法,而是安全和流程:命令该被拦就不能执行,该改写就必须用改写后的内容;钩子给模型补充的大段文字太长时,要写到文件里再给路径;被拦截的用户提示不能偷偷发给模型;插件里的钩子也要生效。没有这些测试,钩子系统很容易在权限、安全、上下文保存或恢复会话时出错。
restrictive_workspace_write_profile62–69 ↗
fn restrictive_workspace_write_profile() -> PermissionProfile
作用:做一个很严格的“工作区可写”权限配置,用来测试越界写文件时必须走审批或钩子流程。
数据流:进去没有参数 → 它调用权限配置生成器,把网络关到受限状态,并排除临时目录等宽松入口 → 出来一个 PermissionProfile,后续测试用它限制文件写入范围。
调用关系:在测试 apply_patch 通过权限请求钩子放行时被使用,给那条测试制造一个本来不容易写出工作区的安全环境。
调用图:调用 1 个内部函数(workspace_write_with);被 1 处调用(permission_request_hook_allows_apply_patch_with_write_alias)。
network_workspace_write_profile71–78 ↗
fn network_workspace_write_profile() -> PermissionProfile
作用:做一个允许网络的工作区写权限配置,用来测试网络访问也能被权限请求钩子批准。
数据流:进去没有参数 → 它创建一个工作区可写、网络开启、临时目录不额外排除的权限档案 → 出来给网络审批测试使用。
调用关系:权限请求钩子绕过网络审批提示的测试会调用它,用来模拟需要网络但仍受权限系统管控的运行环境。
调用图:调用 1 个内部函数(workspace_write_with);被 1 处调用(permission_request_hook_allows_network_approval_without_prompt)。
code_mode_custom_tool_output_text80–95 ↗
fn code_mode_custom_tool_output_text(output_item: &Value) -> String
作用:把 Code Mode 自定义工具返回的不同 JSON 形态统一抽成一段文本,方便测试检查里面有没有关键字。
数据流:进去一个 JSON 输出项 → 它查看 output 字段,如果是字符串就直接取,如果是数组就拼里面的 text,如果是对象就取 content → 出来一段普通字符串;遇到不认识的格式就让测试失败。
调用关系:Code Mode 相关测试用它读取工具输出,比如确认命令被改写、被拦截,或者错误被代码捕获。
调用图:被 3 处调用(assert_post_tool_use_blocks_code_mode_tool_result, pre_tool_use_block_rejects_code_mode_tool_promise_before_execution, pre_tool_use_rewrites_code_mode_nested_exec_command_before_execution);外部调用 2 个(get, panic!)。
non_openai_model_provider97–104 ↗
fn non_openai_model_provider(server: &wiremock::MockServer) -> ModelProviderInfo
作用:造一个指向测试服务器的“非标准 OpenAI 提供商”配置,用来测试某些流程不依赖真实 OpenAI 服务。
数据流:进去一个 mock 服务器 → 它复制内置 openai 提供商配置,改名字、改 base_url,并关闭 websocket 支持 → 出来一个测试专用 ModelProviderInfo。
调用关系:压缩会话相关测试会用它,让所有模型请求都打到本地假服务器。
调用图:被 1 处调用(compact_session_start_hook_records_additional_context_for_next_turn);外部调用 2 个(built_in_model_providers, format!)。
trust_plugin_hooks106–122 ↗
fn trust_plugin_hooks(config: &mut Config, plugin_hook_sources: Vec<PluginHookSource>)
作用:在测试配置里启用插件钩子,并把发现到的插件钩子标记为可信。
数据流:进去一个可修改配置和一批插件钩子来源 → 它开启 CodexHooks 功能,列出钩子,确认至少找到一个,再把这些钩子加入信任列表 → 配置被改成允许这些插件钩子运行。
调用关系:插件钩子测试会用它完成准备工作;它内部把发现钩子的活交给 list_hooks,把信任登记交给 trust_hooks。
调用图:调用 1 个内部函数(trust_hooks);外部调用 3 个(assert!, list_hooks, default)。
write_stop_hook124–169 ↗
fn write_stop_hook(home: &Path, block_prompts: &[&str]) -> Result<()>
作用:往临时 home 目录写一个 Stop 钩子脚本,用来模拟模型回答结束后要求继续重试或放行。
数据流:进去 home 路径和一组要阻塞时返回的提示词 → 它生成 Python 脚本、日志文件路径和 hooks.json → 出来磁盘上有可被测试 Codex 发现的 Stop 钩子。
调用关系:多个 Stop 钩子测试在构建 Codex 前调用它,让后续提交用户消息时触发阻塞、续写和日志记录。
write_parallel_stop_hooks171–211 ↗
fn write_parallel_stop_hooks(home: &Path, prompts: &[&str]) -> Result<()>
作用:写出多个并排的 Stop 钩子,用来测试同一轮里多个钩子同时给出继续提示时是否都被保存。
数据流:进去 home 路径和多条提示词 → 它为每条提示词写一个 Python 脚本,并把它们放进同一个 Stop 配置组 → 出来一个 hooks.json 和多份脚本。
调用关系:多 Stop 钩子持久化测试调用它,然后检查下一次模型请求和 rollout 记录里是否按顺序保留所有提示。
write_user_prompt_submit_hook213–260 ↗
fn write_user_prompt_submit_hook(
home: &Path,
blocked_prompt: &str,
additional_context: &str,
) -> Result<()>
作用:写一个 UserPromptSubmit 钩子,用来模拟用户刚提交提示时被拦截,并附带给下一轮看的额外上下文。
数据流:进去 home、要拦截的提示词和额外上下文 → 它写 Python 脚本记录输入;当 prompt 匹配时输出 block 和 additionalContext → 出来 hooks.json 与日志脚本。
调用关系:用户提示被阻塞、排队提示不丢失等测试会用它,观察被拦的提示不会发给模型,但上下文能保留下来。
write_session_start_and_user_prompt_submit_order_hooks262–320 ↗
fn write_session_start_and_user_prompt_submit_order_hooks(home: &Path) -> Result<()>
作用:写两个只记录顺序的钩子,用来确认会话开始钩子一定早于用户提交钩子运行。
数据流:进去 home 路径 → 它分别写 SessionStart 和 UserPromptSubmit 脚本,二者都往同一个日志文件写事件名和关键信息 → 出来可检查顺序的测试夹具。
调用关系:首轮顺序测试会调用它,之后读取日志,确认启动事件先发生,用户提示事件后发生。
write_pre_tool_use_hook322–399 ↗
fn write_pre_tool_use_hook(
home: &Path,
matcher: Option<&str>,
mode: &str,
reason: &str,
) -> Result<()>
作用:写一个 PreToolUse 钩子,也就是工具运行前的门卫脚本,可放行、拒绝、补充上下文或用退出码阻止。
数据流:进去 home、匹配器、模式和理由 → 它生成 Python 脚本记录工具输入,并按模式输出 deny、additionalContext、两者都有,或退出码 2 → 出来 hooks.json 和日志。
调用关系:大量工具前置钩子测试用它制造拦截 shell、exec、apply_patch、本地函数工具以及 Code Mode 嵌套命令的场景。
调用图:外部调用 6 个(join, String, format!, write, json!, to_string)。
write_updating_pre_tool_use_hook401–447 ↗
fn write_updating_pre_tool_use_hook(
home: &Path,
matcher: &str,
updated_input: &Value,
) -> Result<()>
作用:写一个会改写工具输入的 PreToolUse 钩子,用来测试命令或补丁在执行前能不能被安全替换。
数据流:进去 home、匹配器和新的输入 JSON → 它写脚本记录原始输入,然后返回 permissionDecision allow 和 updatedInput → 出来一个会改写工具参数的钩子配置。
调用关系:命令改写、apply_patch 改写、本地工具改写和 Code Mode 嵌套命令改写测试会用它。
write_pre_tool_use_hook_toml449–515 ↗
fn write_pre_tool_use_hook_toml(
home: &Path,
script_name: &str,
log_name: &str,
matcher: Option<&str>,
mode: &str,
reason: &str,
) -> Result<()>
作用:把 PreToolUse 钩子写进 config.toml,而不是 hooks.json,用来测试两种配置来源都能生效。
数据流:进去 home、脚本名、日志名、匹配器、模式和理由 → 它写 Python 脚本,再写包含 hooks 配置的 TOML 文件 → 出来一个通过 config.toml 定义的钩子。
调用关系:config.toml 钩子阻塞测试和 hooks.json 与 config.toml 合并测试会调用它。
write_permission_request_hook517–588 ↗
fn write_permission_request_hook(
home: &Path,
matcher: Option<&str>,
mode: &str,
reason: &str,
) -> Result<()>
作用:写一个 PermissionRequest 钩子,用来模拟工具需要额外权限时,外部脚本可以自动允许或拒绝。
数据流:进去 home、匹配器、模式和理由 → 它写脚本记录权限请求;按模式输出 allow、deny 或退出码 2 → 出来一个 PermissionRequest 钩子的 hooks.json。
调用关系:install_allow_permission_request_hook 会用它生成常见的放行钩子;权限审批相关测试也会直接调用它定制匹配器。
调用图:被 1 处调用(install_allow_permission_request_hook);外部调用 6 个(join, String, format!, write, json!, to_string)。
install_allow_permission_request_hook590–597 ↗
fn install_allow_permission_request_hook(home: &Path) -> Result<()>
作用:安装一个固定会允许 Bash 权限请求的钩子,减少测试里重复写配置。
数据流:进去 home 路径 → 它调用 write_permission_request_hook,使用默认 Bash 匹配器和 allow 模式 → 出来一个能自动批准 Bash 权限请求的测试钩子。
调用关系:多个权限请求测试用它快速准备“钩子批准,不弹用户审批”的场景。
调用图:调用 1 个内部函数(write_permission_request_hook)。
write_post_tool_use_hook599–669 ↗
fn write_post_tool_use_hook(
home: &Path,
matcher: Option<&str>,
mode: &str,
reason: &str,
) -> Result<()>
作用:写一个 PostToolUse 钩子,也就是工具运行后的检查脚本,可补充上下文或替换工具结果。
数据流:进去 home、匹配器、模式和理由 → 它生成脚本记录工具输入和输出,并按模式返回额外上下文、block、continue false 或退出码 2 → 出来 hooks.json 与日志。
调用关系:工具后置钩子测试用它验证命令结果、apply_patch 结果和 Code Mode 工具 Promise 是否会被正确处理。
调用图:外部调用 6 个(join, String, format!, write, json!, to_string)。
write_logging_pre_and_blocking_post_tool_use_hooks671–727 ↗
fn write_logging_pre_and_blocking_post_tool_use_hooks(home: &Path, feedback: &str) -> Result<()>
作用:同时写一个只记录的前置钩子和一个会阻塞的后置钩子,用来测试长时间 exec 会话结束时的后置处理。
数据流:进去 home 和反馈文本 → 它写两个 Python 脚本,一个记录 PreToolUse 输入,一个记录 PostToolUse 输入后用退出码 2 返回反馈 → 出来同时包含前后钩子的 hooks.json。
调用关系:exec 会话通过 write_stdin 完成的测试会用它,确认后置钩子看到最终输出并替换给模型的结果。
write_session_start_hook_recording_transcript729–764 ↗
fn write_session_start_hook_recording_transcript(home: &Path) -> Result<()>
作用:写一个 SessionStart 钩子,专门记录启动时给它的 transcript_path 是否已经真实存在。
数据流:进去 home 路径 → 它写脚本读取标准输入里的 transcript_path,检查文件是否存在,再写入日志 → 出来可验证转录文件路径的钩子。
调用关系:会话开始路径测试调用它,确认 Codex 在触发钩子前已经把 transcript 文件落到磁盘。
write_session_start_hook_with_context766–796 ↗
fn write_session_start_hook_with_context(home: &Path, additional_context: &str) -> Result<()>
作用:写一个 SessionStart 钩子,让会话开始时给模型补充一段开发者上下文。
数据流:进去 home 和上下文文本 → 它写脚本直接输出 additionalContext,并写 hooks.json → 出来一个启动时注入上下文的钩子。
调用关系:大段启动上下文溢写测试用它,确认太长内容不会直接塞进请求,而是保存到文件。
write_compact_session_start_hook_with_context798–840 ↗
fn write_compact_session_start_hook_with_context(
home: &Path,
additional_context: &str,
) -> Result<()>
作用:写一个只在 compact 来源触发的 SessionStart 钩子,用来测试会话压缩后补充上下文。
数据流:进去 home 和上下文文本 → 它写脚本记录输入,并在匹配 compact 时返回 additionalContext → 出来带 matcher 的 SessionStart 配置。
调用关系:压缩会话测试调用它,验证首次启动不触发,压缩后的下一轮才把上下文送给模型。
write_resume_and_compact_session_start_hook_with_context842–899 ↗
fn write_resume_and_compact_session_start_hook_with_context(
home: &Path,
resume_context: &str,
compact_context: &str,
) -> Result<()>
作用:写一个同时支持 resume 和 compact 两种来源的 SessionStart 钩子,分别返回不同上下文。
数据流:进去 home、恢复上下文和压缩上下文 → 它写脚本读取 source,再按 source 选择对应 additionalContext → 出来两个 matcher 共用一个脚本的 hooks.json。
调用关系:恢复会话再自动压缩的测试会用它,检查 resume 和 compact 两个钩子都按顺序运行。
rollout_hook_prompt_texts901–922 ↗
fn rollout_hook_prompt_texts(text: &str) -> Result<Vec<String>>
作用:从 rollout 记录文件里提取由钩子插入的用户提示片段,方便确认这些片段被持久保存。
数据流:进去 rollout 文件的文本 → 它逐行解析 JSON,筛出用户消息,再识别 hook prompt fragment → 出来一个提示文本列表。
调用关系:Stop 钩子多次阻塞测试用它读取历史记录,确认续写提示没有只停留在内存里。
调用图:调用 1 个内部函数(parse_hook_prompt_fragment);被 1 处调用(stop_hook_can_block_multiple_times_in_same_turn);外部调用 2 个(new, from_str)。
request_hook_prompt_texts924–932 ↗
fn request_hook_prompt_texts(
request: &core_test_support::responses::ResponsesRequest,
) -> Vec<String>
作用:从一次发给模型的请求里取出钩子插入的用户提示片段。
数据流:进去测试捕获的 ResponsesRequest → 它先取 user 角色的输入文本,再解析其中的 hook prompt fragment → 出来一组纯文本提示。
调用关系:Stop 钩子长提示溢写测试用它确认第二次请求确实带上了钩子续写提示。
调用图:调用 1 个内部函数(message_input_texts);被 1 处调用(stop_hook_spills_large_continuation_prompt)。
spilled_hook_output_path934–937 ↗
fn spilled_hook_output_path(text: &str) -> Option<&str>
作用:从提示文本里找出“完整钩子输出保存到哪里”的文件路径。
数据流:进去一段文本 → 它逐行找固定前缀 Full hook output saved to: → 找到就返回路径字符串,找不到就返回空。
调用关系:多个大内容测试用它定位溢写文件,然后检查文件里的完整内容是否没丢。
调用图:被 4 处调用(post_tool_use_spills_large_feedback_message, pre_tool_use_hook_spills_large_additional_context, session_start_hook_spills_large_additional_context, stop_hook_spills_large_continuation_prompt)。
read_stop_hook_inputs939–946 ↗
fn read_stop_hook_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取 Stop 钩子的日志,把每次钩子收到的输入还原成 JSON。
数据流:进去 home 路径 → 它打开 stop_hook_log.jsonl,跳过空行,逐行解析 JSON → 出来一组钩子输入记录。
调用关系:Stop 多次阻塞测试用它检查 turn_id 和 stop_hook_active 是否正确。
调用图:被 1 处调用(stop_hook_can_block_multiple_times_in_same_turn);外部调用 2 个(join, read_to_string)。
read_pre_tool_use_hook_inputs948–950 ↗
fn read_pre_tool_use_hook_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取 PreToolUse 钩子的输入日志,省去每个测试重复写路径和解析代码。
数据流:进去 home 路径 → 它拼出 pre_tool_use_hook_log.jsonl,再交给通用日志读取函数 → 出来 JSON 输入列表。
调用关系:几乎所有前置工具钩子测试都会用它验证钩子看到的工具名、调用 ID 和原始输入。
调用图:调用 1 个内部函数(read_hook_inputs_from_log);被 12 处调用(assert_pre_tool_use_rewrites_bash_surface, post_tool_use_blocks_when_exec_session_completes_via_write_stdin, pre_tool_use_block_rejects_code_mode_tool_promise_before_execution, pre_tool_use_blocks_apply_patch_before_execution, pre_tool_use_blocks_apply_patch_with_write_alias, pre_tool_use_blocks_exec_command_before_execution, pre_tool_use_blocks_local_function_tool_before_execution, pre_tool_use_blocks_shell_command_before_execution, pre_tool_use_merges_hooks_json_and_config_toml, pre_tool_use_rewrites_apply_patch_before_execution (+2 more));外部调用 1 个(join)。
read_permission_request_hook_inputs952–959 ↗
fn read_permission_request_hook_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取 PermissionRequest 钩子的日志,检查权限请求传给钩子的内容是否正确。
数据流:进去 home 路径 → 它读取 permission_request_hook_log.jsonl,逐行解析 JSON → 出来权限请求输入列表。
调用关系:单次权限请求断言函数会调用它,再进一步检查工具名、命令和描述。
调用图:被 1 处调用(assert_single_permission_request_hook_input_for_tool);外部调用 2 个(join, read_to_string)。
assert_permission_request_hook_input961–980 ↗
fn assert_permission_request_hook_input(
hook_input: &Value,
tool_name: &str,
command: &str,
description: Option<&str>,
)
作用:检查一条权限请求钩子输入是否长得像预期,防止多带或少带敏感字段。
数据流:进去一条 JSON、预期工具名、命令和描述 → 它逐项断言事件名、工具输入和不应出现的字段 → 没有返回值;不符合就让测试失败。
调用关系:更高层的单次权限请求断言函数会调用它,集中维护 PermissionRequest 输入格式的要求。
调用图:被 1 处调用(assert_single_permission_request_hook_input_for_tool);外部调用 2 个(assert!, assert_eq!)。
assert_single_permission_request_hook_input982–988 ↗
fn assert_single_permission_request_hook_input(
home: &Path,
command: &str,
description: Option<&str>,
) -> Result<Vec<serde_json::Value>>
作用:检查当前测试里只发生了一次 Bash 权限请求,并且内容符合预期。
数据流:进去 home、命令和可选描述 → 它把工具名固定为 Bash,转交给通用版本检查 → 出来日志 JSON 列表,方便调用方继续看其他字段。
调用关系:shell、exec、网络审批和沙箱重试权限测试会用它做标准校验。
调用图:调用 1 个内部函数(assert_single_permission_request_hook_input_for_tool);被 4 处调用(permission_request_hook_allows_network_approval_without_prompt, permission_request_hook_allows_shell_command_without_user_approval, permission_request_hook_sees_raw_exec_command_input, permission_request_hook_sees_retry_context_after_sandbox_denial)。
assert_single_permission_request_hook_input_for_tool990–1000 ↗
fn assert_single_permission_request_hook_input_for_tool(
home: &Path,
tool_name: &str,
command: &str,
description: Option<&str>,
) -> Result<Vec<serde_json::Value>>
作用:检查指定工具只触发了一次权限请求,并验证那次请求内容。
数据流:进去 home、工具名、命令和描述 → 它读取权限请求日志,确认只有一条,再调用详细断言 → 返回读取到的 JSON 列表。
调用关系:Bash 默认断言函数和 apply_patch 权限测试都会用它。
调用图:调用 2 个内部函数(assert_permission_request_hook_input, read_permission_request_hook_inputs);被 2 处调用(assert_single_permission_request_hook_input, permission_request_hook_allows_apply_patch_with_write_alias);外部调用 1 个(assert_eq!)。
read_post_tool_use_hook_inputs1002–1004 ↗
fn read_post_tool_use_hook_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取 PostToolUse 钩子的输入日志,用来确认工具运行后的输出也被传给了钩子。
数据流:进去 home 路径 → 它拼出 post_tool_use_hook_log.jsonl,交给通用日志读取函数 → 出来 JSON 输入列表。
调用关系:所有后置工具钩子测试都靠它检查 tool_response、tool_input 和调用 ID。
调用图:调用 1 个内部函数(read_hook_inputs_from_log);被 8 处调用(assert_post_tool_use_blocks_code_mode_tool_result, post_tool_use_block_decision_replaces_shell_command_output_with_reason, post_tool_use_blocks_when_exec_session_completes_via_write_stdin, post_tool_use_continue_false_replaces_shell_command_output_with_stop_reason, post_tool_use_exit_two_replaces_one_shot_exec_command_output_with_feedback, post_tool_use_records_additional_context_for_apply_patch, post_tool_use_records_additional_context_for_shell_command, post_tool_use_records_apply_patch_context_with_edit_alias);外部调用 1 个(join)。
read_hook_inputs_from_log1006–1013 ↗
fn read_hook_inputs_from_log(log_path: &Path) -> Result<Vec<serde_json::Value>>
作用:通用的 JSONL 钩子日志读取器,很多具体读取函数都复用它。
数据流:进去日志文件路径 → 它读取文件内容,跳过空行,逐行解析成 JSON → 出来一组日志记录。
调用关系:前置、后置、顺序、插件和 config.toml 钩子测试都通过它读取脚本写下的日志。
调用图:被 6 处调用(plugin_pre_tool_use_blocks_shell_command_before_execution, pre_tool_use_blocks_shell_when_defined_in_config_toml, pre_tool_use_merges_hooks_json_and_config_toml, read_hook_order_inputs, read_post_tool_use_hook_inputs, read_pre_tool_use_hook_inputs);外部调用 1 个(read_to_string)。
read_session_start_hook_inputs1015–1022 ↗
fn read_session_start_hook_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取 SessionStart 钩子的输入日志,检查会话来源和 transcript 路径等启动信息。
数据流:进去 home 路径 → 它读取 session_start_hook_log.jsonl 并逐行解析 → 出来启动钩子输入列表。
调用关系:会话启动、压缩和恢复相关测试用它验证 source 字段和执行次数。
调用图:被 3 处调用(compact_session_start_hook_records_additional_context_for_next_turn, resumed_thread_runs_resume_then_compact_session_start_hooks, session_start_hook_sees_materialized_transcript_path);外部调用 2 个(join, read_to_string)。
read_user_prompt_submit_hook_inputs1024–1031 ↗
fn read_user_prompt_submit_hook_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取 UserPromptSubmit 钩子的输入日志,确认每个用户提示都被钩子看到了。
数据流:进去 home 路径 → 它读取 user_prompt_submit_hook_log.jsonl,逐行解析 JSON → 出来提示提交钩子输入列表。
调用关系:用户提示被阻塞和排队提示测试用它检查 prompt 顺序和 turn_id。
调用图:被 2 处调用(blocked_queued_prompt_does_not_strand_earlier_accepted_prompt, blocked_user_prompt_submit_persists_additional_context_for_next_turn);外部调用 2 个(join, read_to_string)。
read_hook_order_inputs1033–1035 ↗
fn read_hook_order_inputs(home: &Path) -> Result<Vec<serde_json::Value>>
作用:读取专门用于检查钩子执行顺序的日志。
数据流:进去 home 路径 → 它拼 hook_order_log.jsonl,再交给通用日志读取器 → 出来按写入顺序排列的 JSON 记录。
调用关系:会话开始先于用户提示提交的测试用它拿到事件顺序。
调用图:调用 1 个内部函数(read_hook_inputs_from_log);被 1 处调用(session_start_runs_before_user_prompt_submit_on_first_turn);外部调用 1 个(join)。
ev_message_item_done1037–1047 ↗
fn ev_message_item_done(id: &str, text: &str) -> Value
作用:生成一个模拟 SSE 事件,表示助手消息这个输出项已经完成。
数据流:进去消息 ID 和文本 → 它组装成符合响应流格式的 JSON → 出来一个 Value,供假服务器发送。
调用关系:流式排队提示测试会把它包进 SSE 数据,模拟模型逐步输出后完成消息。
调用图:外部调用 1 个(json!)。
sse_event1049–1051 ↗
request_message_input_texts1053–1066 ↗
fn request_message_input_texts(body: &[u8], role: &str) -> Vec<String>
作用:从原始 HTTP 请求体里提取某个角色的输入文本。
数据流:进去请求字节和角色名 → 它把字节解析成 JSON,遍历 input 里的 message 和 input_text → 出来对应角色的文本列表。
调用关系:排队提示测试用它直接检查第二次模型请求中有没有包含或排除某些用户提示。
调用图:被 1 处调用(blocked_queued_prompt_does_not_strand_earlier_accepted_prompt);外部调用 1 个(from_slice)。
stop_hook_can_block_multiple_times_in_same_turn1069–1174 ↗
async fn stop_hook_can_block_multiple_times_in_same_turn() -> Result<()>
作用:测试 Stop 钩子在同一轮里连续阻塞多次时,Codex 会一次次带着续写提示重试。
数据流:进去测试运行环境 → 它搭假服务器、写 Stop 钩子、提交一次用户消息 → 最后检查模型请求次数、钩子输入、turn_id、active 标志和 rollout 记录。
调用关系:这是 Stop 钩子核心流程测试,依赖写钩子、读日志、解析请求和解析 rollout 的辅助函数。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_stop_hook_inputs, rollout_hook_prompt_texts);外部调用 5 个(assert!, assert_eq!, read_to_string, skip_if_no_network!, vec!)。
session_start_hook_sees_materialized_transcript_path1177–1213 ↗
async fn session_start_hook_sees_materialized_transcript_path() -> Result<()>
作用:测试 SessionStart 钩子收到的 transcript_path 不是空的,而且文件已经存在。
数据流:进去测试环境 → 它写记录 transcript 的启动钩子,提交首轮消息 → 最后读取日志确认路径和 exists 为真。
调用关系:它验证会话启动阶段的文件准备顺序,依赖启动钩子夹具和日志读取函数。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_session_start_hook_inputs);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
session_start_runs_before_user_prompt_submit_on_first_turn1216–1260 ↗
async fn session_start_runs_before_user_prompt_submit_on_first_turn() -> Result<()>
作用:测试第一轮对话里,SessionStart 一定先于 UserPromptSubmit 执行。
数据流:进去测试环境 → 它写两个记录顺序的钩子,提交 hello → 读取顺序日志,确认事件列表是 SessionStart 再 UserPromptSubmit。
调用关系:它保护钩子生命周期顺序,避免用户提示钩子早于会话初始化。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_hook_order_inputs);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
session_start_hook_spills_large_additional_context1263–1302 ↗
async fn session_start_hook_spills_large_additional_context() -> Result<()>
作用:测试启动钩子返回很长上下文时,系统会把完整内容写到文件,而不是直接塞满模型请求。
数据流:进去测试环境 → 它写返回超长 additionalContext 的启动钩子,提交消息 → 从模型请求里找溢写路径,再读文件比对完整内容。
调用关系:它依赖 spilled_hook_output_path 来定位保存文件,验证长文本保护机制。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, spilled_hook_output_path);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
pre_tool_use_hook_spills_large_additional_context1305–1360 ↗
async fn pre_tool_use_hook_spills_large_additional_context() -> Result<()>
作用:测试工具运行前钩子返回很长补充上下文时,也会走溢写文件机制。
数据流:进去测试环境 → 假模型先要求运行 shell,再第二次响应;钩子返回超长上下文 → 测试检查第二次请求里的开发者消息指向完整文件。
调用关系:它把 PreToolUse 和长上下文溢写流程连起来,确认工具阶段与启动阶段行为一致。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, spilled_hook_output_path);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
compact_session_start_hook_records_additional_context_for_next_turn1363–1435 ↗
async fn compact_session_start_hook_records_additional_context_for_next_turn() -> Result<()>
作用:测试会话压缩后,匹配 compact 的 SessionStart 钩子会把上下文加到下一轮模型请求里。
数据流:进去测试环境 → 它提交一轮、触发 Compact、等待完成、再提交一轮 → 检查初始请求没有该上下文,压缩后的请求有,并读取钩子日志确认 source 是 compact。
调用关系:它使用非 OpenAI 测试提供商和启动日志读取函数,覆盖压缩生命周期里的钩子触发。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, non_openai_model_provider, read_session_start_hook_inputs);外部调用 5 个(assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
resumed_thread_runs_resume_then_compact_session_start_hooks1438–1531 ↗
async fn resumed_thread_runs_resume_then_compact_session_start_hooks() -> Result<()>
作用:测试恢复旧会话时先运行 resume 钩子,随后如果自动压缩又运行 compact 钩子。
数据流:进去测试环境 → 先建立一段超 token 限制的会话,再用 rollout 恢复,提交新消息 → 检查最终请求同时包含 resume 和 compact 上下文,日志顺序也是 resume、compact。
调用关系:它覆盖会话恢复和自动压缩两个阶段叠加时的钩子顺序。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_session_start_hook_inputs);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
stop_hook_spills_large_continuation_prompt1534–1581 ↗
async fn stop_hook_spills_large_continuation_prompt() -> Result<()>
作用:测试 Stop 钩子给出的续写提示太长时,会被截断显示并把完整文本保存到文件。
数据流:进去测试环境 → 它写返回超长 block reason 的 Stop 钩子,提交一次消息触发重试 → 检查第二次请求里的 hook prompt 包含溢写提示,并读文件核对完整文本。
调用关系:它把 Stop 钩子续写和溢写机制结合起来,使用 request_hook_prompt_texts 和 spilled_hook_output_path。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, request_hook_prompt_texts, spilled_hook_output_path);外部调用 5 个(assert!, assert_eq!, skip_if_no_network!, repeat_n, vec!)。
resumed_thread_keeps_stop_continuation_prompt_in_history1584–1646 ↗
async fn resumed_thread_keeps_stop_continuation_prompt_in_history() -> Result<()>
作用:测试 Stop 钩子产生的续写提示在恢复会话后仍留在历史里。
数据流:进去测试环境 → 初始会话触发 Stop 钩子并写 rollout,随后用同一 rollout 恢复,再提交新消息 → 检查恢复后的模型请求仍包含原来的钩子提示。
调用关系:它保护 rollout 持久化和恢复逻辑,防止钩子上下文只在当前进程里有效。
调用图:调用 5 个内部函数(mount_sse_once, mount_sse_sequence, sse, start_mock_server, test_codex);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
multiple_blocking_stop_hooks_persist_multiple_hook_prompt_fragments1649–1706 ↗
async fn multiple_blocking_stop_hooks_persist_multiple_hook_prompt_fragments() -> Result<()>
作用:测试多个 Stop 钩子同时阻塞时,所有提示片段都会按顺序进入请求和历史记录。
数据流:进去测试环境 → 它写两个并行 Stop 钩子,提交消息 → 检查第二次请求和 rollout 文件里都有两段续写提示。
调用关系:它依赖并行 Stop 夹具和 rollout 解析,验证多钩子聚合行为。
调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 4 个(assert_eq!, read_to_string, skip_if_no_network!, vec!)。
blocked_user_prompt_submit_persists_additional_context_for_next_turn1709–1781 ↗
async fn blocked_user_prompt_submit_persists_additional_context_for_next_turn() -> Result<()>
作用:测试用户提示被 UserPromptSubmit 钩子拦下后,提示本身不发给模型,但钩子给的上下文会留到下一轮。
数据流:进去测试环境 → 第一次提交被拦提示,第二次提交正常提示 → 检查模型请求只含第二次用户提示,并含第一次钩子保存的开发者上下文;再查日志顺序。
调用关系:它验证用户输入入口的安全性,依赖用户提示钩子夹具和日志读取函数。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_user_prompt_submit_hook_inputs);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
blocked_queued_prompt_does_not_strand_earlier_accepted_prompt1784–1940 ↗
async fn blocked_queued_prompt_does_not_strand_earlier_accepted_prompt() -> Result<()>
作用:测试模型还在流式输出时排队的多个用户提示,即使后面的被钩子拦截,前面已接受的也不会卡住或丢失。
数据流:进去测试环境 → 它用流式假服务器卡住第一轮完成,期间提交一个可接受排队提示和一个会被拦提示 → 放开服务器后检查第二次请求只包含已接受的排队提示。
调用关系:它覆盖异步排队场景,使用流式 SSE 服务器、用户提示钩子日志和原始请求解析函数。
调用图:调用 4 个内部函数(start_streaming_sse_server, test_codex, read_user_prompt_submit_hook_inputs, request_message_input_texts);外部调用 11 个(default, from_millis, from_secs, assert!, assert_eq!, wait_for_event, channel, skip_if_no_network!, sleep, timeout (+1 more))。
permission_request_hook_allows_shell_command_without_user_approval1943–2013 ↗
async fn permission_request_hook_allows_shell_command_without_user_approval() -> Result<()>
作用:测试需要用户审批的 shell 命令可以被 PermissionRequest 钩子自动批准,从而不弹用户确认。
数据流:进去测试环境 → 它写允许钩子,准备一个会删除标记文件的命令,让模型调用 shell → 最后确认命令执行、第二次请求收到工具输出、权限钩子日志正确。
调用关系:它验证权限请求钩子能替代人工审批,使用标准权限请求断言函数。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_permission_request_hook_input);外部调用 8 个(assert!, assert_eq!, format!, write, json!, skip_if_no_network!, temp_dir, vec!)。
permission_request_hook_allows_apply_patch_with_write_alias2016–2085 ↗
async fn permission_request_hook_allows_apply_patch_with_write_alias() -> Result<()>
作用:测试 apply_patch 这种写文件工具可以被 Write 别名匹配的权限请求钩子批准。
数据流:进去测试环境 → 它写 matcher 为 Write 的允许钩子,使用严格工作区权限,让模型申请写出工作区附近文件 → 检查文件被创建且钩子看到 apply_patch 输入。
调用关系:它连接 apply_patch、权限别名和严格权限档案,调用 restrictive_workspace_write_profile。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_permission_request_hook_input_for_tool, restrictive_workspace_write_profile);外部调用 5 个(assert!, assert_eq!, format!, skip_if_no_network!, vec!)。
permission_request_hook_sees_raw_exec_command_input2088–2162 ↗
async fn permission_request_hook_sees_raw_exec_command_input() -> Result<()>
作用:测试统一 exec 工具请求权限时,钩子能看到原始命令和 justification 描述。
数据流:进去测试环境 → 它启用 UnifiedExec,模型调用 exec_command 并带 justification → 钩子允许后命令执行,日志里应有命令和描述。
调用关系:它验证新 exec 工具与 PermissionRequest 输入格式兼容。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_permission_request_hook_input, read_only);外部调用 8 个(assert!, assert_eq!, format!, write, json!, skip_if_no_network!, temp_dir, vec!)。
permission_request_hook_allows_network_approval_without_prompt2165–2284 ↗
async fn permission_request_hook_allows_network_approval_without_prompt() -> Result<()>
作用:测试网络访问需要批准时,PermissionRequest 钩子能自动批准,不产生用户审批事件。
数据流:进去测试环境 → 它配置受管网络、允许钩子和网络权限,模型运行访问测试域名的命令 → 等待钩子日志出现,并确认没有 ExecApprovalRequest 事件。
调用关系:它覆盖网络权限这一特殊审批路径,使用 network_workspace_write_profile 和权限请求断言。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_permission_request_hook_input, network_workspace_write_profile);外部调用 14 个(clone, new, from_millis, from_secs, new, assert!, managed_network_requirements_loader, wait_for_event, write, json! (+4 more))。
permission_request_hook_sees_retry_context_after_sandbox_denial2288–2349 ↗
async fn permission_request_hook_sees_retry_context_after_sandbox_denial() -> Result<()>
作用:测试沙箱先拒绝命令后重试申请权限时,PermissionRequest 钩子仍能看到正确命令。
数据流:进去测试环境 → 它在只读权限下运行写文件命令,失败后触发审批,钩子允许 → 最后文件写成,并检查钩子日志。
调用关系:它只在非 Linux 条件下启用,用来覆盖平台相关的沙箱失败重试流程。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_permission_request_hook_input, read_only);外部调用 6 个(assert_eq!, format!, remove_file, json!, skip_if_no_network!, vec!)。
pre_tool_use_blocks_shell_command_before_execution2352–2443 ↗
async fn pre_tool_use_blocks_shell_command_before_execution() -> Result<()>
作用:测试 PreToolUse 钩子能在 shell 命令真正执行前把它拦住。
数据流:进去测试环境 → 它写拒绝 Bash 的前置钩子,模型要求写标记文件 → 最后工具输出显示被钩子阻止,标记文件不存在,日志包含 transcript_path 和 turn_id。
调用关系:这是前置工具钩子最基础的安全测试,验证阻断发生在执行之前。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 8 个(assert!, assert_eq!, format!, remove_file, json!, skip_if_no_network!, temp_dir, vec!)。
pre_tool_use_records_additional_context_for_shell_command2446–2505 ↗
async fn pre_tool_use_records_additional_context_for_shell_command() -> Result<()>
作用:测试 PreToolUse 钩子不拦命令时,也能给下一次模型请求补充上下文。
数据流:进去测试环境 → 钩子返回 additionalContext,shell 命令正常输出 → 第二次模型请求应包含这段开发者上下文和工具输出。
调用关系:它证明前置钩子不仅能拦截,也能给模型加提示。
调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
blocked_pre_tool_use_records_additional_context_for_shell_command2508–2578 ↗
async fn blocked_pre_tool_use_records_additional_context_for_shell_command() -> Result<()>
作用:测试 PreToolUse 钩子一边拦截命令,一边留下额外上下文给模型。
数据流:进去测试环境 → 钩子返回 deny 和 additionalContext,命令不执行 → 第二次请求既包含钩子上下文,也包含被阻止的工具输出说明。
调用关系:它覆盖拒绝和补充上下文同时出现的组合行为。
调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 8 个(assert!, assert_eq!, format!, remove_file, json!, skip_if_no_network!, temp_dir, vec!)。
BashRewriteSurface::slug2587–2592 ↗
fn slug(self) -> &'static str
作用:给 Bash 改写测试里的不同工具表面生成短名字,方便拼测试 ID 和文件名。
数据流:进去一个枚举值 → 它把 ExecCommand 映射成 exec-command,把 ShellCommand 映射成 shell-command → 出来静态字符串。
调用关系:通用 Bash 改写断言函数用它让同一套测试逻辑适配两种工具。
调用图:被 1 处调用(assert_pre_tool_use_rewrites_bash_surface)。
BashRewriteSurface::tool_call2594–2607 ↗
fn tool_call(self, call_id: &str, command_text: &str) -> Result<Value>
作用:按不同 Bash 工具表面生成对应的模型工具调用事件。
数据流:进去枚举值、调用 ID 和命令文本 → 它为 exec_command 生成 cmd 参数,为 shell_command 生成 command 参数 → 出来一个 JSON 事件。
调用关系:Bash 改写通用测试用它喂给假模型服务器,模拟模型调用不同命令工具。
调用图:调用 1 个内部函数(ev_function_call);外部调用 2 个(json!, to_string)。
BashRewriteSurface::original_command2609–2615 ↗
fn original_command(self, marker: &Path) -> String
作用:生成测试用的原始命令,这条命令如果被执行会写 original 标记。
数据流:进去枚举值和标记文件路径 → 它格式化一条 printf original 的 shell 命令 → 出来命令字符串。
调用关系:Bash 改写测试用它确认原始命令没有被执行。
调用图:被 1 处调用(assert_pre_tool_use_rewrites_bash_surface);外部调用 1 个(format!)。
BashRewriteSurface::rewritten_command2617–2623 ↗
fn rewritten_command(self, marker: &Path) -> String
作用:生成测试用的改写后命令,这条命令执行后会写 rewritten 标记。
数据流:进去枚举值和标记文件路径 → 它格式化一条 printf rewritten 的 shell 命令 → 出来命令字符串。
调用关系:Bash 改写测试用它确认钩子改写后的命令才被执行。
调用图:被 1 处调用(assert_pre_tool_use_rewrites_bash_surface);外部调用 1 个(format!)。
BashRewriteSurface::configure2625–2634 ↗
fn configure(self, config: &mut Config)
作用:按工具表面配置测试环境,必要时打开统一 exec 功能。
数据流:进去枚举值和可变 Config → 它先信任发现到的钩子;如果是 ExecCommand,就启用实验性 UnifiedExec 功能 → 配置被改好。
调用关系:通用 Bash 改写测试在构建 Codex 前调用它,让 shell_command 和 exec_command 两条路都能复用。
调用图:调用 1 个内部函数(trust_discovered_hooks);外部调用 1 个(matches!)。
assert_pre_tool_use_rewrites_bash_surface2637–2703 ↗
async fn assert_pre_tool_use_rewrites_bash_surface(surface: BashRewriteSurface) -> Result<()>
作用:通用测试函数,验证 PreToolUse 能改写 Bash 类命令,无论入口是 shell_command 还是 exec_command。
数据流:进去一个 BashRewriteSurface → 它搭假服务器、写改写钩子、提交请求 → 最后确认原始标记没写、改写标记写了,且钩子日志看到的是原始输入。
调用关系:shell 命令改写测试和 exec 命令改写测试都调用它,避免重复写两份几乎一样的测试。
调用图:调用 7 个内部函数(mount_sse_sequence, start_mock_server, test_codex, original_command, rewritten_command, slug, read_pre_tool_use_hook_inputs);被 2 处调用(pre_tool_use_rewrites_exec_command_before_execution, pre_tool_use_rewrites_shell_command_before_execution);外部调用 8 个(assert!, assert_eq!, format!, remove_file, json!, skip_if_no_network!, temp_dir, vec!)。
pre_tool_use_rewrites_shell_command_before_execution2706–2708 ↗
async fn pre_tool_use_rewrites_shell_command_before_execution() -> Result<()>
作用:测试 shell_command 在执行前能被 PreToolUse 钩子改写。
数据流:进去测试环境 → 它调用通用 Bash 改写断言并选择 ShellCommand 表面 → 测试结果由通用函数完成检查。
调用关系:它是通用改写测试的一个具体入口。
调用图:调用 1 个内部函数(assert_pre_tool_use_rewrites_bash_surface)。
pre_tool_use_rewrites_exec_command_before_execution2711–2713 ↗
async fn pre_tool_use_rewrites_exec_command_before_execution() -> Result<()>
作用:测试 exec_command 在执行前能被 PreToolUse 钩子改写。
数据流:进去测试环境 → 它调用通用 Bash 改写断言并选择 ExecCommand 表面 → 通用函数启用对应配置并完成检查。
调用关系:它是统一 exec 工具改写路径的具体测试入口。
调用图:调用 1 个内部函数(assert_pre_tool_use_rewrites_bash_surface)。
pre_tool_use_rewrites_code_mode_nested_exec_command_before_execution2716–2803 ↗
async fn pre_tool_use_rewrites_code_mode_nested_exec_command_before_execution() -> Result<()>
作用:测试 Code Mode 里的代码调用 exec_command 时,嵌套命令也能被 PreToolUse 改写。
数据流:进去测试环境 → 模型返回一段会调用 tools.exec_command 的代码;钩子把命令换成另一条 → 最后检查输出只包含改写结果,原始文件没创建。
调用关系:它连接 Code Mode、自定义工具输出解析和前置钩子改写逻辑。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, code_mode_custom_tool_output_text, read_pre_tool_use_hook_inputs);外部调用 8 个(new, assert!, assert_eq!, format!, json!, to_string, skip_if_no_network!, vec!)。
pre_tool_use_block_rejects_code_mode_tool_promise_before_execution2806–2878 ↗
async fn pre_tool_use_block_rejects_code_mode_tool_promise_before_execution() -> Result<()>
作用:测试 Code Mode 里被 PreToolUse 拦截的工具调用,会以 Promise 错误形式返回给代码,而且命令不执行。
数据流:进去测试环境 → 模型代码 try/catch 调用 exec_command;钩子拒绝 → 输出应显示 caught 和拒绝理由,标记文件不存在。
调用关系:它保护 Code Mode 的错误传播语义,防止被拦命令仍悄悄执行。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, code_mode_custom_tool_output_text, read_pre_tool_use_hook_inputs);外部调用 7 个(new, assert!, assert_eq!, format!, to_string, skip_if_no_network!, vec!)。
assert_post_tool_use_blocks_code_mode_tool_result2880–2966 ↗
async fn assert_post_tool_use_blocks_code_mode_tool_result(
hook_mode: &'static str,
reason: &'static str,
) -> Result<()>
作用:通用测试函数,验证 PostToolUse 能在 Code Mode 中拦截已经执行完的工具结果。
数据流:进去钩子模式和理由 → 它运行一段 Code Mode 代码调用命令,命令先执行,再由后置钩子阻止结果返回 → 检查代码捕获错误、原始结果没泄露、日志记录了真实输出。
调用关系:两个后置 Code Mode 阻断测试分别用 block 决策和退出码 2 调用它。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, code_mode_custom_tool_output_text, read_post_tool_use_hook_inputs);被 2 处调用(post_tool_use_block_decision_rejects_code_mode_tool_promise, post_tool_use_exit_two_rejects_code_mode_tool_promise);外部调用 7 个(new, assert!, assert_eq!, format!, to_string, skip_if_no_network!, vec!)。
post_tool_use_block_decision_rejects_code_mode_tool_promise2969–2975 ↗
async fn post_tool_use_block_decision_rejects_code_mode_tool_promise() -> Result<()>
作用:测试 PostToolUse 用 JSON block 决策时,会拒绝 Code Mode 里的工具 Promise。
数据流:进去测试环境 → 它调用通用后置阻断函数,模式设为 decision_block → 通用函数完成执行、拦截和断言。
调用关系:它覆盖后置钩子的一种阻断表达方式。
调用图:调用 1 个内部函数(assert_post_tool_use_blocks_code_mode_tool_result)。
post_tool_use_exit_two_rejects_code_mode_tool_promise2978–2981 ↗
async fn post_tool_use_exit_two_rejects_code_mode_tool_promise() -> Result<()>
作用:测试 PostToolUse 脚本用退出码 2 失败时,也会拒绝 Code Mode 工具 Promise。
数据流:进去测试环境 → 它调用通用后置阻断函数,模式设为 exit_2 → 通用函数检查错误传播和结果隐藏。
调用关系:它覆盖后置钩子用进程退出码表达阻断的路径。
调用图:调用 1 个内部函数(assert_post_tool_use_blocks_code_mode_tool_result)。
plugin_pre_tool_use_blocks_shell_command_before_execution2984–3132 ↗
async fn plugin_pre_tool_use_blocks_shell_command_before_execution() -> Result<()>
作用:测试插件提供的 PreToolUse 钩子也能在 shell 命令执行前拦截。
数据流:进去测试环境 → 它手工搭一个插件目录、插件 manifest、插件 hooks.json 和脚本,再启用插件功能 → 提交命令后确认输出显示插件钩子拦截,标记文件没创建。
调用关系:它使用 trust_plugin_hooks 把插件钩子加入信任范围,覆盖插件来源的钩子加载和执行。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_hook_inputs_from_log, try_from);外部调用 13 个(clone, new, new, assert!, assert_eq!, format!, create_dir_all, remove_file, write, json! (+3 more))。
pre_tool_use_blocks_shell_when_defined_in_config_toml3135–3216 ↗
async fn pre_tool_use_blocks_shell_when_defined_in_config_toml() -> Result<()>
作用:测试写在 config.toml 里的 PreToolUse 钩子能阻止 shell 命令。
数据流:进去测试环境 → 它写 TOML 钩子配置和脚本,模型要求运行命令 → 最后确认工具输出带阻断理由,命令没有创建标记文件,日志记录正确。
调用关系:它验证 hooks.json 之外的配置入口也能驱动前置钩子。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_hook_inputs_from_log);外部调用 8 个(assert!, assert_eq!, format!, remove_file, json!, skip_if_no_network!, temp_dir, vec!)。
pre_tool_use_merges_hooks_json_and_config_toml3219–3317 ↗
async fn pre_tool_use_merges_hooks_json_and_config_toml() -> Result<()>
作用:测试 hooks.json 和 config.toml 中定义的 PreToolUse 钩子会合并执行,而不是互相覆盖。
数据流:进去测试环境 → 它同时写两个来源的钩子,让命令正常运行 → 检查两个日志文件都记录了同一次工具调用。
调用关系:它保护配置合并逻辑,避免用户在两个地方配置钩子时丢掉其中一个。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_hook_inputs_from_log, read_pre_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
pre_tool_use_blocks_exec_command_before_execution3320–3398 ↗
async fn pre_tool_use_blocks_exec_command_before_execution() -> Result<()>
作用:测试统一 exec_command 在执行前能被 PreToolUse 钩子用退出码 2 拦住。
数据流:进去测试环境 → 它启用 UnifiedExec,写拒绝 Bash 的前置钩子,模型调用 exec_command → 检查阻断输出、命令未执行、日志含原始 command 和 turn_id。
调用关系:它覆盖 exec_command 路径的前置安全门。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 8 个(assert!, assert_eq!, format!, remove_file, json!, skip_if_no_network!, temp_dir, vec!)。
pre_tool_use_blocks_apply_patch_before_execution3401–3470 ↗
async fn pre_tool_use_blocks_apply_patch_before_execution() -> Result<()>
作用:测试 apply_patch 在真正修改文件前能被 PreToolUse 钩子阻止。
数据流:进去测试环境 → 模型请求添加文件的 patch,钩子按 apply_patch 匹配并拒绝 → 最后文件不存在,工具输出包含阻断理由。
调用关系:它验证文件修改工具也受前置钩子管控。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, format!, skip_if_no_network!, vec!)。
pre_tool_use_rewrites_apply_patch_before_execution3473–3540 ↗
async fn pre_tool_use_rewrites_apply_patch_before_execution() -> Result<()>
作用:测试 apply_patch 的补丁内容能在执行前被 PreToolUse 改写。
数据流:进去测试环境 → 原始 patch 要创建 original 文件,钩子把输入改成创建 rewritten 文件 → 最后 original 不存在,rewritten 文件内容正确。
调用关系:它覆盖前置钩子对写文件工具参数的改写能力。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 6 个(assert!, assert_eq!, format!, json!, skip_if_no_network!, vec!)。
pre_tool_use_blocks_apply_patch_with_write_alias3543–3608 ↗
async fn pre_tool_use_blocks_apply_patch_with_write_alias() -> Result<()>
作用:测试 PreToolUse 用 Write 别名也能匹配并阻止 apply_patch。
数据流:进去测试环境 → 钩子 matcher 是 Write,模型调用 apply_patch → 输出显示被拦,目标文件没有创建,日志里的真实工具名仍是 apply_patch。
调用关系:它验证工具别名匹配不会改变日志里的实际工具身份。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, format!, skip_if_no_network!, vec!)。
pre_tool_use_blocks_local_function_tool_before_execution3611–3669 ↗
async fn pre_tool_use_blocks_local_function_tool_before_execution() -> Result<()>
作用:测试本地函数工具也能在调用前被 PreToolUse 钩子拦截。
数据流:进去测试环境 → 模型调用 test_sync_tool,钩子按工具名拒绝 → 第二次请求里的工具输出说明该工具被钩子阻止。
调用关系:它把前置钩子覆盖范围扩展到本地函数工具,而不仅是 shell 和补丁。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
pre_tool_use_rewrites_local_function_tool_before_execution3672–3731 ↗
async fn pre_tool_use_rewrites_local_function_tool_before_execution() -> Result<()>
作用:测试本地函数工具的参数可以被 PreToolUse 钩子改写后再执行。
数据流:进去测试环境 → 模型给 test_sync_tool 传一个会出问题的原始参数,钩子改成空参数 → 工具返回 ok,日志里仍记录原始参数。
调用关系:它验证前置钩子既能保护本地工具,也能修正模型给出的参数。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_pre_tool_use_hook_inputs);外部调用 4 个(assert_eq!, json!, skip_if_no_network!, vec!)。
post_tool_use_records_additional_context_for_shell_command3734–3820 ↗
async fn post_tool_use_records_additional_context_for_shell_command() -> Result<()>
作用:测试 shell 命令执行后,PostToolUse 钩子能看到输出并给下一次模型请求补充上下文。
数据流:进去测试环境 → 命令输出 post-tool-output,后置钩子返回 additionalContext → 检查第二次请求包含工具输出和开发者上下文,日志里有 tool_response、transcript_path、turn_id。
调用关系:这是后置工具钩子的基础正向测试。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
post_tool_use_block_decision_replaces_shell_command_output_with_reason3823–3880 ↗
async fn post_tool_use_block_decision_replaces_shell_command_output_with_reason() -> Result<()>
作用:测试 PostToolUse 用 block 决策时,给模型看的 shell 输出会被替换成钩子理由。
数据流:进去测试环境 → shell 实际输出 blocked-output,后置钩子返回 decision block 和 reason → 第二次请求里的工具输出应等于 reason,日志仍记录原始输出。
调用关系:它验证后置钩子可以隐藏或替换不想给模型看的结果。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs);外部调用 4 个(assert_eq!, json!, skip_if_no_network!, vec!)。
post_tool_use_continue_false_replaces_shell_command_output_with_stop_reason3883–3941 ↗
async fn post_tool_use_continue_false_replaces_shell_command_output_with_stop_reason() -> Result<()>
作用:测试 PostToolUse 返回 continue false 时,也会用 stopReason 替换工具输出。
数据流:进去测试环境 → 命令输出 stop-output,钩子返回 continue false 和 stopReason → 给模型的输出变成 stopReason,日志保存原始输出。
调用关系:它覆盖另一种后置阻断格式。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs);外部调用 4 个(assert_eq!, json!, skip_if_no_network!, vec!)。
post_tool_use_exit_two_replaces_one_shot_exec_command_output_with_feedback3944–4010 ↗
async fn post_tool_use_exit_two_replaces_one_shot_exec_command_output_with_feedback() -> Result<()>
作用:测试 exec_command 一次性运行后,PostToolUse 脚本用退出码 2 时会把输出替换成反馈文本。
数据流:进去测试环境 → 启用 UnifiedExec,命令输出 post-hook-output,钩子 stderr 写反馈并退出 2 → 第二次请求的工具输出变成反馈。
调用关系:它覆盖统一 exec 工具的后置失败反馈路径。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs);外部调用 4 个(assert_eq!, json!, skip_if_no_network!, vec!)。
post_tool_use_spills_large_feedback_message4013–4075 ↗
async fn post_tool_use_spills_large_feedback_message() -> Result<()>
作用:测试后置钩子的阻断反馈很长时,会被溢写到文件。
数据流:进去测试环境 → exec 命令正常输出,后置钩子返回超长反馈 → 给模型的工具输出包含截断提示和文件路径,读文件应得到完整反馈。
调用关系:它验证后置阻断文本也遵守长内容保护机制。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, spilled_hook_output_path);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
post_tool_use_blocks_when_exec_session_completes_via_write_stdin4078–4177 ↗
async fn post_tool_use_blocks_when_exec_session_completes_via_write_stdin() -> Result<()>
作用:测试长时间 exec 会话通过 write_stdin 轮询完成时,PostToolUse 仍会在最终输出后运行并可阻断结果。
数据流:进去测试环境 → 模型先启动 exec 会话,再用 write_stdin 等待完成;前置钩子记录开始,后置钩子阻断最终输出 → 检查给模型的轮询输出被替换,日志看到最终会话输出。
调用关系:它覆盖多步 exec 会话,保证后置钩子不是只对一次性命令生效。
调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs, read_pre_tool_use_hook_inputs);外部调用 6 个(assert!, assert_eq!, json!, skip_if_no_network!, skip_if_windows!, vec!)。
post_tool_use_records_additional_context_for_apply_patch4180–4248 ↗
async fn post_tool_use_records_additional_context_for_apply_patch() -> Result<()>
作用:测试 apply_patch 成功后,PostToolUse 钩子能记录补丁结果并补充上下文。
数据流:进去测试环境 → 模型应用一个新增文件补丁,后置钩子返回上下文 → 检查文件存在,第二次请求包含上下文,日志里的 tool_response 显示 patch 成功。
调用关系:它验证后置钩子覆盖文件修改工具。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, format!, skip_if_no_network!, vec!)。
post_tool_use_records_apply_patch_context_with_edit_alias4251–4314 ↗
async fn post_tool_use_records_apply_patch_context_with_edit_alias() -> Result<()>
作用:测试 PostToolUse 用 Edit 别名也能匹配 apply_patch,并补充上下文。
数据流:进去测试环境 → 钩子 matcher 是 Edit,模型调用 apply_patch 新增文件 → 文件创建成功,第二次请求包含上下文,日志记录实际工具名和 patch 内容。
调用关系:它验证后置钩子的工具别名匹配,和 apply_patch 实际执行结果能一起工作。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_post_tool_use_hook_inputs);外部调用 5 个(assert!, assert_eq!, format!, skip_if_no_network!, vec!)。
core/tests/suite/hooks_mcp.rs源码 ↗
这里测试的是 MCP(Model Context Protocol,一种让 Codex 连接外部工具的协议)和 hook(钩子,像门口的检查员,在工具执行前后插一脚)能不能配合好。测试会临时写出几个 Python hook 脚本:有的在工具执行前直接拒绝,有的把工具输入改掉,有的在工具执行后补充上下文。然后它启动一个假的模型服务器,让模型“要求调用 echo 工具”,再启动一个真实的测试 MCP 服务来回显消息。测试最后检查三件事:工具有没有被拦住或改写;hook 收到的输入是否正确写进日志;工具执行后的额外信息有没有被放进下一次请求里。文件还同时覆盖两种工具命名方式:老式带 mcp__ 前缀的名字,以及新式不带前缀的名字。
enable_mcp_tool_name_features37–41 ↗
fn enable_mcp_tool_name_features(config: &mut Config, prefix_mcp_tool_names: bool)
作用:这个函数用来切换 MCP 工具名的模式。测试需要同时验证老式“带前缀工具名”和新式“不带前缀工具名”,它就是负责打开对应功能开关的小帮手。
数据流:进去的是一份可修改的配置和一个布尔值,表示是否使用带前缀的 MCP 工具名。它检查这个布尔值:如果不要前缀,就在配置里启用“非前缀 MCP 工具名”功能。出来时没有单独返回值,但配置已经被改好。
调用关系:它不是单独跑的测试,而是被 enable_hooks_and_rmcp_server 调用。整体测试搭环境时,先信任 hook,再通过它决定工具名格式,最后再插入测试 MCP 服务。
调用图:被 1 处调用(enable_hooks_and_rmcp_server)。
write_pre_tool_use_hook43–84 ↗
fn write_pre_tool_use_hook(home: &Path, reason: &str) -> Result<()>
作用:这个函数在临时测试目录里写一个“工具执行前 hook”。这个 hook 会拒绝 MCP 工具调用,用来测试 Codex 是否真的能在工具还没执行前把它拦下来。
数据流:进去的是测试用的 home 目录和拒绝原因。它生成一个 Python 脚本,脚本会读取 Codex 传来的 JSON 输入,写进日志文件,然后输出一段 JSON,告诉 Codex:这次 PreToolUse 要拒绝,并附上原因。函数还会写 hooks.json,让 Codex 知道这个脚本该在指定 MCP 工具前运行。出来时文件系统里多了脚本、日志目标路径和 hook 配置。
调用关系:它通常通过 test_codex 的预构建步骤执行,在测试真正启动 Codex 前准备好 hook。后面的 pre_tool_use_blocks_mcp_tool_before_execution 会触发这个 hook,并检查它是否成功拦截工具。
write_updating_pre_tool_use_hook86–128 ↗
fn write_updating_pre_tool_use_hook(home: &Path, updated_message: &str) -> Result<()>
作用:这个函数写一个“工具执行前但允许执行”的 hook。它不是阻止工具,而是把工具输入里的 message 改成新的内容,用来验证 hook 能不能改写 MCP 工具参数。
数据流:进去的是测试 home 目录和要替换成的新消息。它写出一个 Python 脚本,脚本读取原始工具输入、保存到日志,然后输出 JSON,声明允许执行,并给出 updatedInput,把 message 改掉。它同时写 hooks.json,把这个脚本挂到目标 MCP 工具上。结果是后续工具调用会收到被改过的输入,而不是模型原本给的输入。
调用关系:它由 pre_tool_use_rewrites_mcp_tool_before_execution 的测试准备阶段使用。测试流程会先让模型请求 echo 工具,再通过这个 hook 改写参数,最后检查 echo 的结果确实来自新消息。
write_post_tool_use_hook130–171 ↗
fn write_post_tool_use_hook(home: &Path, additional_context: &str) -> Result<()>
作用:这个函数写一个“工具执行后 hook”。它用来测试工具已经执行完之后,Codex 能不能把工具输入、工具输出交给 hook,并把 hook 给出的补充说明带到下一轮模型请求里。
数据流:进去的是测试 home 目录和一段额外上下文文字。它生成一个 Python 脚本,脚本读取工具执行后的 payload,写入日志,然后输出 JSON,里面带有 PostToolUse 的 additionalContext。它还写 hooks.json,让 Codex 在目标 MCP 工具执行后运行这个脚本。出来时,测试目录中具备了后置 hook 的全部配置。
调用关系:它在 post_tool_use_records_mcp_tool_payload_and_context 的搭建阶段被调用。后续测试会执行 MCP echo 工具,再确认这个 hook 收到了完整信息,并且 additionalContext 被加进下一次发给模型的内容里。
read_hook_inputs173–180 ↗
fn read_hook_inputs(home: &Path, log_name: &str) -> Result<Vec<Value>>
作用:这个函数读取 hook 脚本写下的日志。测试用它确认 Codex 传给 hook 的内容是否正确,比如工具名、工具输入、工具输出和记录文件路径。
数据流:进去的是测试 home 目录和日志文件名。它打开对应文件,把每一行非空文本当作一条 JSON 解析,最后返回 JSON 列表。它不改配置,也不启动服务,只把 hook 留下的“收据”读出来给断言使用。
调用关系:它被三个主要测试共用:拦截前置 hook、改写前置 hook、后置 hook。那些测试先触发工具调用,再用它读取日志,最后比较日志内容是否符合预期。
调用图:被 3 处调用(post_tool_use_records_mcp_tool_payload_and_context, pre_tool_use_blocks_mcp_tool_before_execution, pre_tool_use_rewrites_mcp_tool_before_execution);外部调用 2 个(join, read_to_string)。
insert_rmcp_test_server182–214 ↗
fn insert_rmcp_test_server(config: &mut Config, command: String, approval_mode: AppToolApproval)
作用:这个函数把一个测试用的 MCP 服务塞进 Codex 配置里。没有它,Codex 就不知道要去哪里找这个 rmcp echo 工具,后面的工具调用测试也就跑不起来。
数据流:进去的是可修改配置、测试 MCP 服务的命令路径,以及工具调用的审批模式。它复制当前 MCP 服务配置,新增一个叫 rmcp 的服务,传输方式是标准输入输出,也就是通过进程的 stdin/stdout 说话,并设置启动超时、是否启用、默认审批模式等。最后把新服务表写回配置。
调用关系:它由 enable_hooks_and_rmcp_server 调用,是测试环境搭建的一部分。前面配置 hook 和工具名特性,随后它把实际可调用的 MCP 测试服务接进去。
调用图:被 1 处调用(enable_hooks_and_rmcp_server);外部调用 3 个(from_secs, new, new)。
enable_hooks_and_rmcp_server216–225 ↗
fn enable_hooks_and_rmcp_server(
config: &mut Config,
rmcp_test_server_bin: String,
approval_mode: AppToolApproval,
prefix_mcp_tool_names: bool,
)
作用:这个函数是一键搭测试环境的小总管。它把 hook 信任、MCP 工具名模式、rmcp 测试服务器三件事一次性配置好。
数据流:进去的是 Codex 配置、rmcp 测试服务程序路径、审批模式和工具名是否加前缀。它先让测试发现到的 hook 被信任,再按需要打开非前缀工具名功能,最后把 rmcp MCP 服务写进配置。出来时配置已经能运行这些 MCP hook 测试。
调用关系:多个测试在 with_config 阶段调用它。它把工作分给 trust_discovered_hooks、enable_mcp_tool_name_features 和 insert_rmcp_test_server,让每个测试不用重复写同样的环境准备代码。
调用图:调用 3 个内部函数(trust_discovered_hooks, enable_mcp_tool_name_features, insert_rmcp_test_server)。
pre_tool_use_blocks_mcp_tool_before_execution_with_legacy_prefixed_names228–234 ↗
async fn pre_tool_use_blocks_mcp_tool_before_execution_with_legacy_prefixed_names() -> Result<()>
作用:这是一个具体测试入口,验证老式带前缀的 MCP 工具名也能被 PreToolUse hook 拦住。它保证旧命名方式不会绕过安全检查。
数据流:它没有外部输入。运行时把“使用前缀工具名”设为 true,并传入带 mcp__ 前缀的命名空间,然后等待通用测试函数完成。结果是如果工具没有被 hook 拦住,测试就会失败。
调用关系:它本身只负责选择测试参数,真正搭服务器、写 hook、提交对话、检查结果的工作都交给 pre_tool_use_blocks_mcp_tool_before_execution。
调用图:调用 1 个内部函数(pre_tool_use_blocks_mcp_tool_before_execution)。
pre_tool_use_blocks_mcp_tool_before_execution_with_non_prefixed_names237–243 ↗
async fn pre_tool_use_blocks_mcp_tool_before_execution_with_non_prefixed_names() -> Result<()>
作用:这是另一个具体测试入口,验证新式不带前缀的 MCP 工具名也会被 PreToolUse hook 拦住。它防止新命名功能上线后破坏原有的 hook 安全行为。
数据流:它没有外部输入。运行时把“使用前缀工具名”设为 false,并传入不带前缀的命名空间,然后调用通用测试函数。出来的结果是一次完整断言:新命名下工具也必须先经过 hook。
调用关系:它和 legacy 版本是一对参数化测试。两者都把主要流程交给 pre_tool_use_blocks_mcp_tool_before_execution,只是传入的工具命名方式不同。
调用图:调用 1 个内部函数(pre_tool_use_blocks_mcp_tool_before_execution)。
pre_tool_use_blocks_mcp_tool_before_execution245–332 ↗
async fn pre_tool_use_blocks_mcp_tool_before_execution(
prefix_mcp_tool_names: bool,
mcp_namespace: &'static str,
) -> Result<()>
作用:这个通用测试验证:当 MCP 工具将要执行时,PreToolUse hook 如果说“拒绝”,工具就不能真的执行,并且拒绝原因要反馈给模型。它测试的是工具执行前的安全闸门。
数据流:进去的是是否使用前缀工具名,以及模型调用工具时使用的命名空间。它先跳过无网络环境,再启动假模型服务器,安排模型先请求调用 rmcp echo 工具,之后再回复一条普通消息。接着它写入会拒绝调用的前置 hook,配置并启动测试 Codex 和 MCP 服务,提交一轮用户请求。最后它检查第二次模型请求里的工具输出:里面必须说明工具被 PreToolUse hook 拦截,并包含拒绝原因和工具名;还会读取 hook 日志,确认 hook 收到了正确的工具名、调用编号、输入和实际存在的 transcript 文件路径。
调用关系:它被两个具体测试入口调用,分别覆盖带前缀和不带前缀工具名。它内部会用 mock SSE 响应模拟模型事件,用 test_codex 启动被测系统,用 wait_for_mcp_server 等待 MCP 服务就绪,并用 read_hook_inputs 检查 hook 实际收到什么。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_hook_inputs);被 2 处调用(pre_tool_use_blocks_mcp_tool_before_execution_with_legacy_prefixed_names, pre_tool_use_blocks_mcp_tool_before_execution_with_non_prefixed_names);外部调用 7 个(assert!, assert_eq!, stdio_server_bin, wait_for_mcp_server, json!, skip_if_no_network!, vec!)。
pre_tool_use_rewrites_mcp_tool_before_execution335–407 ↗
async fn pre_tool_use_rewrites_mcp_tool_before_execution() -> Result<()>
作用:这个测试验证 PreToolUse hook 不只可以拦截工具,还可以在工具执行前改写输入。它确保工具真正收到的是 hook 改过的参数,而不是模型最初给的参数。
数据流:它没有外部参数。运行时启动假模型服务器,让模型请求调用 rmcp echo,并传入原始 message。测试准备一个前置 hook,把 message 改成 rewritten_message,然后配置 Codex 和 MCP 服务。提交用户请求后,它检查后续请求里的工具输出,必须包含改写后的 echo 内容,且不能包含原始消息。最后它读取 hook 日志,确认 hook 看到的仍然是原始输入,因为改写发生在 hook 输出之后。
调用关系:这是一个独立的 tokio 异步测试。它使用 write_updating_pre_tool_use_hook 准备改写脚本,用 mount_sse_once 安排模型响应,用 read_hook_inputs 校验日志,并依赖测试 MCP echo 服务来证明改写真的影响了工具执行。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_hook_inputs);外部调用 7 个(assert!, assert_eq!, stdio_server_bin, wait_for_mcp_server, json!, skip_if_no_network!, vec!)。
post_tool_use_records_mcp_tool_payload_and_context_with_legacy_prefixed_names410–417 ↗
async fn post_tool_use_records_mcp_tool_payload_and_context_with_legacy_prefixed_names() -> Result<()>
作用:这是一个具体测试入口,验证老式带前缀 MCP 工具名下,PostToolUse hook 能收到工具执行信息,并能补充上下文。它保证旧工具名路径上的后置 hook 没有断。
数据流:它没有外部输入。运行时传入“使用前缀工具名”为 true 和带前缀命名空间,然后调用通用后置 hook 测试。测试失败与否取决于后置 hook 是否正确记录并影响下一次模型请求。
调用关系:它只负责选择老式命名参数。真正的服务器搭建、工具调用、日志检查和上下文检查,都交给 post_tool_use_records_mcp_tool_payload_and_context。
调用图:调用 1 个内部函数(post_tool_use_records_mcp_tool_payload_and_context)。
post_tool_use_records_mcp_tool_payload_and_context_with_non_prefixed_names420–427 ↗
async fn post_tool_use_records_mcp_tool_payload_and_context_with_non_prefixed_names() -> Result<()>
作用:这是新式不带前缀 MCP 工具名的后置 hook 测试入口。它确认即使工具名格式变了,PostToolUse hook 仍能拿到同样完整的工具信息。
数据流:它没有外部输入。运行时传入“使用前缀工具名”为 false 和不带前缀命名空间,然后调用通用测试函数。结果是验证新命名模式下的后置 hook 行为和旧模式一致。
调用关系:它和 legacy 版本共同复用 post_tool_use_records_mcp_tool_payload_and_context。这样同一套核心断言可以覆盖两种 MCP 工具命名方式。
调用图:调用 1 个内部函数(post_tool_use_records_mcp_tool_payload_and_context)。
post_tool_use_records_mcp_tool_payload_and_context429–532 ↗
async fn post_tool_use_records_mcp_tool_payload_and_context(
prefix_mcp_tool_names: bool,
mcp_namespace: &'static str,
) -> Result<()>
作用:这个通用测试验证 MCP 工具执行完之后,PostToolUse hook 能拿到工具输入和输出,并且它返回的额外上下文会被带到下一次模型请求里。它测试的是工具执行后的记录和补充说明机制。
数据流:进去的是是否使用前缀工具名,以及模型调用工具时使用的命名空间。它先启动假模型服务器,安排模型请求调用 rmcp echo,再安排下一轮普通回复。然后它写入后置 hook,配置并启动 Codex 与 rmcp MCP 服务,提交用户请求。工具执行后,测试检查下一次发给模型的请求里是否包含 hook 给出的 post_context,同时确认工具输出仍然包含 echo 结果。最后它读取 hook 日志,检查 hook 收到的事件名、工具名、调用编号、原始输入、结构化工具响应,以及 transcript 文件路径是否真实存在。
调用关系:它被带前缀和不带前缀两个测试入口复用。它内部把模型侧交给 mock SSE,把被测系统交给 test_codex,把 MCP 服务就绪检查交给 wait_for_mcp_server,把 hook 日志解析交给 read_hook_inputs。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_hook_inputs);被 2 处调用(post_tool_use_records_mcp_tool_payload_and_context_with_legacy_prefixed_names, post_tool_use_records_mcp_tool_payload_and_context_with_non_prefixed_names);外部调用 7 个(assert!, assert_eq!, stdio_server_bin, wait_for_mcp_server, json!, skip_if_no_network!, vec!)。
core/tests/suite/user_notification.rs源码 ↗
这个测试文件只在非 Windows 系统上跑,因为它要创建并执行一个 Unix shell 脚本。它先启动一个假的服务器,假装模型返回了一句“Done”并结束本轮对话;再临时写出一个 notify.sh 通知脚本,这个脚本会把收到的最后一个参数写进 notify.txt。接着测试启动一套配置了通知脚本的 Codex 实例,向它提交一条普通用户输入“hello world”。等系统处理完这一轮后,测试会等待 notify.txt 出现,读取里面的 JSON 通知内容,并检查三件事:通知类型是不是“agent-turn-complete”,输入消息是不是原来的“hello world”,最后一条助手消息是不是“Done”。这就像模拟一个门铃:任务完成时系统要按响门铃,还要把“谁来了、说了什么”写清楚。
summarize_context_three_requests_and_instructions26–82 ↗
async fn summarize_context_three_requests_and_instructions() -> anyhow::Result<()>
作用:这个测试函数验证“一轮对话完成后执行通知脚本”这件事是否真的发生,并且通知里带的内容是否正确。它用假的服务器和临时脚本搭了一个小现场,避免依赖真实模型服务和真实用户环境。
数据流:进去的是测试代码自己搭好的环境:一个 mock server(假的网络服务器)、一段预设的模型返回内容、一个临时目录里的 notify.sh 脚本,以及用户输入“hello world”。函数把这些组装成一个配置了通知命令的测试版 Codex,提交用户输入,等待回合完成;之后读取脚本写出的 notify.txt,把里面的字符串解析成 JSON。出来的结果不是返回业务数据,而是通过断言确认通知 JSON 里的 type、input-messages、last-assistant-message 都符合预期;同时它会在临时目录中创建脚本和通知文件。
调用关系:它是这个测试文件的唯一测试入口,由 Tokio 测试运行器自动执行。流程开始时它会用 start_mock_server、sse 和 mount_sse_once 准备假的模型响应;然后用 test_codex 构建带通知配置的测试实例;运行中通过 codex.submit 提交用户输入,并用 wait_for_event 等待系统发出回合完成事件;最后借助 fs_wait 等待脚本落盘,再用 assert_eq! 检查通知内容是否正确。
调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 13 个(default, from_secs, new, assert_eq!, from_mode, wait_for_event, wait_for_path_exists, from_str, skip_if_no_network!, set_permissions (+3 more))。