Core src 工具和 unified-exec 测试
这一阶段主要是给“工具调用”和“统一执行命令”做验收,属于幕后质检,不是开机或关机。它像一套安全检查台:先校对各类工具的说明书,保证模型按正确格式调用;再检查路由、注册表、追踪记录,确保工具不被派错、漏记;同时盯住补丁、命令、联网、沙箱和审批,防止越权。最后用 unified-exec 的测试模拟本地和远程跑命令、超时、截断输出、清理进程,保证真正干活时稳住。
工具规格和处理器契约
这些测试锁定已发布的工具 schema,以及定义核心工具界面的专项处理器行为。
core/src/tools/handlers/test_sync_spec.rs源码 ↗
这个文件不是在真正执行等待,而是在告诉系统:有一个叫 test_sync_tool 的内部测试工具,它能接收哪些参数、这些参数是什么意思。可以把它理解成一张“工具使用说明表”。测试时,多个并发调用可以用同一个 barrier(屏障,像约好几个人到齐才开门的门禁)来碰头;还可以设置调用前后睡眠多久,用来模拟慢操作。文件用 JsonSchema(JSON 参数格式说明)把输入形状写清楚,比如 barrier 里必须有 id 和 participants,timeout_ms 可选。最后它把这些说明包成 ToolSpec,供上层注册工具时使用。这样测试代码不用猜参数格式,系统也能知道这个工具该怎样暴露给调用方。
create_test_sync_tool6–59 ↗
fn create_test_sync_tool() -> ToolSpec
作用:创建 test_sync_tool 这个测试同步工具的规格说明。别人调用它时,会拿到一份完整的工具定义,包括工具名、用途说明、允许传入哪些参数。
数据流:进去没有外部参数;函数自己组装两层参数说明:先写 barrier 里面的 id、participants、timeout_ms,再写最外层的 sleep_before_ms、sleep_after_ms 和 barrier。最后输出一个 ToolSpec::Function,也就是一份可注册到系统里的工具说明;它不修改外部状态。
调用关系:它通常由上层的 spec 在准备工具清单时调用。函数内部把具体字段交给 JsonSchema::string、JsonSchema::number、JsonSchema::object 这些小工具来描述字符串、数字和对象,最后用 ResponsesApiTool 和 ToolSpec::Function 包成系统认识的工具规格。
调用图:调用 3 个内部函数(number, object, string);被 1 处调用(spec);外部调用 3 个(from, Function, vec!)。
core/src/tools/handlers/test_sync.rs源码 ↗
这个文件解决的是“怎么稳定地测试多个工具调用同时发生”的问题。平时并发测试很容易碰运气:有的任务先跑,有的任务后跑,结果不稳定。这里提供了一个测试工具,可以按参数要求先暂停一段时间,也可以进入一个“栅栏”。栅栏可以理解成集合点:说好 3 个人到齐再出发,那前 2 个到了也只能等,第 3 个到了大家才一起继续。文件里用一个全局表按 id 保存这些栅栏,避免不同测试互相混在一起;还会检查参与人数不能乱改,并设置超时,防止测试永远卡住。调用成功后返回简单的 “ok”。这让测试代码可以精确制造等待、并发、超时等情况,而不用靠不可靠的时间猜测。
default_timeout_ms52–54 ↗
fn default_timeout_ms() -> u64
作用:给栅栏等待时间提供默认值。调用方如果没有写 timeout_ms,就默认最多等 1000 毫秒,避免测试无限卡住。
数据流:进去没有任何输入 → 它直接读取文件里写死的默认超时时间常量 → 出来一个数字 1000,供参数解析时当默认超时使用。
调用关系:它不是业务流程里的主动步骤,而是被参数反序列化机制间接使用:当 TestSyncArgs 里的 barrier 没有给 timeout_ms 时,这个函数提供默认值。
barrier_map56–58 ↗
fn barrier_map() -> &'static tokio::sync::Mutex<HashMap<String, BarrierState>>
作用:拿到保存所有栅栏的全局表。这个表用栅栏 id 找到对应的等待点,让多次工具调用能约定在同一个地方会合。
数据流:进去没有普通参数 → 它检查全局存储有没有初始化;如果没有,就新建一个带互斥锁的 HashMap,互斥锁就是一把锁,防止多个异步任务同时改同一张表 → 出来的是这张全局表的引用。
调用关系:wait_on_barrier 在需要查找、创建或删除栅栏时会调用它。它是所有栅栏共享状态的入口,保证不同并发调用能看到同一份记录。
调用图:被 1 处调用(wait_on_barrier)。
TestSyncHandler::tool_name61–63 ↗
fn tool_name(&self) -> ToolName
作用:告诉工具系统:这个处理器对应的工具名叫 test_sync_tool。没有这个名字,外部请求就不知道该把调用交给谁。
数据流:进去是处理器自身 → 它构造一个普通工具名 test_sync_tool → 出来一个 ToolName,供工具注册和匹配使用。
调用关系:工具注册表会问每个处理器叫什么名。这个函数通过 plain 创建名字,让系统能把名为 test_sync_tool 的请求路由到 TestSyncHandler。
调用图:调用 1 个内部函数(plain)。
TestSyncHandler::spec65–67 ↗
fn spec(&self) -> ToolSpec
作用:返回这个测试同步工具的说明书。说明书告诉模型或调用方:这个工具支持哪些参数,比如睡眠时间、栅栏 id、参与人数和超时时间。
数据流:进去是处理器自身 → 它调用 create_test_sync_tool 生成工具规格 → 出来一个 ToolSpec,供工具系统展示和校验使用。
调用关系:工具系统在发布或注册工具能力时会调用它。它把具体规格生成工作交给 create_test_sync_tool,自己只负责把结果交回去。
调用图:调用 1 个内部函数(create_test_sync_tool)。
TestSyncHandler::supports_parallel_tool_calls69–71 ↗
fn supports_parallel_tool_calls(&self) -> bool
作用:明确告诉系统:这个工具允许并行调用。这个声明很关键,因为栅栏功能本来就是为了让多个调用同时等在一起。
数据流:进去是处理器自身 → 它不读取额外信息,直接返回 true → 出来的是“支持并行”的判断结果。
调用关系:工具调度器在决定能不能同时运行多个 test_sync_tool 调用时会看这个结果。这里返回 true,才让 wait_on_barrier 这种多人会合的测试场景有意义。
TestSyncHandler::handle73–75 ↗
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:这是工具系统真正调用处理器时的入口。它把一次工具调用包装成异步任务,然后交给 handle_call 去做具体事情。
数据流:进去是一份 ToolInvocation,也就是一次工具调用请求 → 它把 self.handle_call(invocation) 包成可以等待的异步结果 → 出来一个 future,future 可以理解成“稍后会完成的任务”。
调用关系:工具执行框架调用 handle。handle 不直接解析参数或等待栅栏,而是把活儿转交给 TestSyncHandler::handle_call,并用 pin 固定异步任务的位置,方便运行时安全地执行。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
TestSyncHandler::handle_call79–116 ↗
async fn handle_call(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>
作用:执行 test_sync_tool 的核心流程:读参数、按要求睡眠、必要时等待栅栏,最后返回 ok。它把测试想制造的时间和并发效果真正做出来。
数据流:进去是一份工具调用 → 它先取出函数调用参数;如果收到的不是函数参数,就返回给模型看的错误信息;然后用 parse_arguments 把文本参数解析成结构化数据;如果设置了 sleep_before_ms,就先睡对应毫秒;如果设置了 barrier,就调用 wait_on_barrier 等其他参与者;如果设置了 sleep_after_ms,就再睡一次 → 出来是一个文本输出 “ok”,并标记成功;过程中可能因为参数错误、载荷类型不对或栅栏超时返回错误。
调用关系:TestSyncHandler::handle 会把请求交给它。它是整个处理流程的指挥者:参数解析交给 parse_arguments,真正的栅栏等待交给 wait_on_barrier,最终用 FunctionToolOutput::from_text 和 boxed_tool_output 把结果包装成工具系统能理解的输出。
调用图:调用 4 个内部函数(from_text, boxed_tool_output, parse_arguments, wait_on_barrier);被 1 处调用(handle);外部调用 3 个(from_millis, sleep, RespondToModel)。
wait_on_barrier121–176 ↗
async fn wait_on_barrier(args: BarrierArgs) -> Result<(), FunctionCallError>
作用:让一个调用在指定栅栏前等待,直到约定数量的调用都到齐,或者等到超时。它是这个测试工具里最重要的同步机制。
数据流:进去是 BarrierArgs,里面有栅栏 id、需要到齐的人数和超时时间 → 它先检查人数和超时必须大于 0;然后从全局栅栏表里按 id 找已有栅栏,没有就新建;如果同一个 id 已经存在但参与人数不一致,就返回错误,避免同一个集合点规则前后矛盾;接着在指定毫秒数内等待栅栏放行 → 如果超时,出来是错误;如果所有人到齐,出来是成功;最后由其中一个“领头”的等待者清理全局表里的这条栅栏记录。
调用关系:TestSyncHandler::handle_call 在参数里包含 barrier 时调用它。它自己会用 barrier_map 取得共享栅栏表,用 tokio 的 Barrier 做异步等待,用 timeout 防止一直挂住;等所有参与者通过后,再安全地把这次用完的栅栏从表里移除。
调用图:调用 1 个内部函数(barrier_map);被 1 处调用(handle_call);外部调用 7 个(new, ptr_eq, new, from_millis, format!, timeout, RespondToModel)。
core/src/tools/handlers/test_sync_spec_tests.rs源码 ↗
这个文件只做一件事:拿实际生成出来的 test_sync_tool 工具规格,和代码里写死的“期望版本”逐项对比。可以把它理解成给工具菜单拍了一张标准照片,以后每次改代码都拿新照片和标准照片比一比。这里检查的内容包括工具名、说明文字、是否严格校验、输入参数长什么样。输入里有 barrier,也就是“栅栏”:让多个并发调用在同一个点等齐再继续;还有 sleep_before_ms 和 sleep_after_ms,用来在动作前后故意等一会儿。这个测试的重要点是,它不测试工具真正怎么等待,而是测试工具对外声明的参数格式是否准确。这样可以避免内部改动不小心破坏协议,导致依赖它的集成测试看不懂该怎么调用。
test_sync_tool_matches_expected_spec7–64 ↗
fn test_sync_tool_matches_expected_spec()
作用:这个测试函数确认 create_test_sync_tool() 生成的工具说明,必须和预期的工具说明完全一样。有人改了工具名、参数、必填项或说明文字时,它会第一时间报错。
数据流:进去的是 create_test_sync_tool() 生成的实际工具规格,以及函数里手写的一份期望工具规格。它用 assert_eq!(断言相等,也就是不一样就让测试失败)把两边逐项比较。出来的结果不是普通返回值,而是测试通过或失败;如果失败,会告诉开发者实际说明和期望说明哪里不一致。
调用关系:它在测试运行时由 Rust 的测试框架自动调用。它把核心检查交给 assert_eq! 完成;assert_eq! 负责比较两个工具规格是否完全一致。这个函数的作用像守门员,挡住那些会让 test_sync_tool 对外规格意外变化的代码改动。
调用图:外部调用 1 个(assert_eq!)。
core/src/tools/handlers/agent_jobs_spec_tests.rs源码 ↗
这份文件不是真正执行代理任务的代码,而是像一张“验收清单”。系统里有两个给模型调用的工具:一个叫 spawn_agents_on_csv,会按 CSV(一种表格文件)里的每一行启动一个子代理去干活;另一个叫 report_agent_job_result,让这些子代理把结果交回来。这个测试文件逐项检查它们暴露给外部的工具说明是否准确:工具叫什么、文字描述是什么、哪些参数必须填、哪些参数可选、参数是不是字符串、数字、布尔值或对象。这里的 JSON Schema 可以理解成“参数填写规则说明书”,告诉调用方该交什么格式的数据。这样做很重要,因为这些工具通常是给大模型或外部接口看的;如果说明或必填项错了,调用方可能少传 CSV 路径、少传指令模板,或者结果没有按要求上报,最后就会造成批处理失败但原因很难查。
described_object6–14 ↗
fn described_object(description: &str) -> JsonSchema
作用:这个小帮手用来快速造一个“对象类型”的 JSON Schema,并给它加上一段说明文字。测试里多次需要这种“结果对象”规则,所以把重复写法收起来。
数据流:进去的是一段说明文字 → 它先调用 JsonSchema::object 做出一个空的对象规则,再把这段文字放进规则的 description 字段 → 出来的是一个带说明的对象型 schema,供后面的断言拿来比较。
调用关系:它是两个测试里的配套工具,主要服务于构造期望值。它自己把创建对象 schema 的细节交给 object,还会把输入文字转成新的字符串;测试函数再拿它生成的 schema 去和真实工具规格做比较。
spawn_agents_on_csv_tool_requires_csv_and_instruction17–85 ↗
fn spawn_agents_on_csv_tool_requires_csv_and_instruction()
作用:这个测试确认 spawn_agents_on_csv 工具的公开规格没有走样,尤其是 CSV 路径和处理指令这两个关键参数必须填写。没有这两个参数,系统就不知道读哪个表、每一行该让子代理做什么。
数据流:进去的是测试代码里手写的一份“正确工具规格” → 它调用真实的 create_spawn_agents_on_csv_tool() 生成当前代码实际提供的工具规格,再用 assert_eq! 把两边逐项比较 → 如果完全一样,测试通过;如果名字、描述、参数类型、必填项等任何地方变了,测试就失败并指出差异。
调用关系:它在测试运行时由 Rust 测试框架自动调用。它不执行真正的 CSV 任务,只检查工具入口说明是否符合预期;比较动作交给外部的 assert_eq! 断言宏完成。
调用图:外部调用 1 个(assert_eq!)。
report_agent_job_result_tool_requires_result_payload88–126 ↗
fn report_agent_job_result_tool_requires_result_payload()
作用:这个测试确认 report_agent_job_result 工具必须要求子代理提交 job_id、item_id 和 result。这样主任务才能知道是哪一个任务、哪一行数据、返回了什么结果。
数据流:进去的是测试里写好的期望工具规格 → 它调用真实的 create_report_agent_job_result_tool() 得到当前实现生成的规格,再用 assert_eq! 做完整比较 → 输出不是业务结果,而是测试是否通过;如果必填字段、参数说明或类型被改错,测试会失败。
调用关系:它同样由测试框架自动运行,专门守住“子代理上报结果”这个工具的接口契约。它把最终判断交给 assert_eq!,从而确保真实创建函数产出的规格和手册式的期望完全一致。
调用图:外部调用 1 个(assert_eq!)。
core/src/tools/handlers/agent_jobs_tests.rs源码 ↗
这个文件不直接做业务,而是像一组验收题,专门检查别的代码是否按预期工作。这里重点测的是处理表格和模板时的边角情况:CSV 里字段可能带逗号和引号,写回 CSV 时也要正确加引号;用户写的指令模板里,像 {path} 这样的占位符要能替换成某一行数据里的值,而 {{literal}} 这种双大括号要保留下来当普通文字;如果模板里写了不存在的字段,就不要乱替换;如果 CSV 表头重复,也要报一个清楚的错误。没有这些测试,代码以后被修改时,很可能悄悄破坏这些规则,导致批量任务读错文件、生成错指令,或者给用户一个难懂的失败结果。
parse_csv_supports_quotes_and_commas6–17 ↗
fn parse_csv_supports_quotes_and_commas()
作用:这个测试确认 CSV 解析器能正确读懂带引号、带逗号的字段。也就是说,字段里的逗号不应该被误认为是新的一列。
数据流:进去的是一小段 CSV 文本,里面有表头 id,name,还有一行名字写成 "alpha, beta"。测试把这段文本交给 parse_csv,然后检查出来的表头是不是 id 和 name,数据行是不是两行,并且 alpha, beta 是否仍然是一个完整字段。出来的结果不是给用户用,而是测试通过或失败。
调用关系:它由测试框架在跑测试时调用。它把真正的解析工作交给 parse_csv,然后用 assert_eq! 这个断言工具对比“实际结果”和“应该得到的结果”,用来守住 CSV 解析规则。
调用图:外部调用 1 个(assert_eq!)。
csv_escape_quotes_when_needed20–24 ↗
fn csv_escape_quotes_when_needed()
作用:这个测试确认把文字写成 CSV 字段时,该加引号的时候会加,该转义引号的时候会转义。这样生成的 CSV 才不会被别的软件读错。
数据流:进去的是几种简单字符串:普通文字 simple、带逗号的 a,b、带双引号的 a"b。测试分别交给 csv_escape,检查普通文字保持不变,带逗号的文字被包上双引号,内部双引号被写成两个双引号。出来的是一组断言结果,表示转义规则有没有被破坏。
调用关系:它由测试框架执行,专门围绕 csv_escape 这个小工具。它只调用 assert_eq! 来核对结果,作用是确保以后改 CSV 输出代码时不会漏掉逗号和引号这两个常见坑。
调用图:外部调用 1 个(assert_eq!)。
render_instruction_template_expands_placeholders_and_escapes_braces27–41 ↗
fn render_instruction_template_expands_placeholders_and_escapes_braces()
作用:这个测试确认指令模板能把 {path} 这类占位符替换成数据里的值,同时能把双大括号写法当成普通大括号。它保证用户批量生成任务说明时,模板不会乱变形。
数据流:进去的是一份 JSON 行数据,里面有 path、area、file path 这些字段,以及一段模板文字。测试调用 render_instruction_template 后,检查 {path}、{area}、{file path} 是否被换成对应内容,{{literal}} 是否变成普通的 {literal}。出来的是渲染后的完整文字,并通过断言确认它完全符合预期。
调用关系:它由测试框架调用。它先用 json! 构造一份像表格行一样的数据,再把模板渲染交给 render_instruction_template,最后用 assert_eq! 检查结果,覆盖了“字段名带空格”和“字面大括号”这两个容易漏测的情况。
调用图:外部调用 2 个(assert_eq!, json!)。
render_instruction_template_leaves_unknown_placeholders44–50 ↗
fn render_instruction_template_leaves_unknown_placeholders()
作用:这个测试确认模板里遇到未知占位符时,会把它原样留下,而不是删掉或替换成空。这样用户能看出哪里写错了,也不会悄悄丢信息。
数据流:进去的是只包含 path 字段的一份 JSON 数据,以及模板 Check {path} then {missing}。测试调用 render_instruction_template 后,检查 {path} 被替换成 src/lib.rs,而没有对应数据的 {missing} 仍然保留。出来的是一段渲染后的文字,测试用它判断未知字段的处理是否安全。
调用关系:它由测试框架运行,关注 render_instruction_template 在数据不完整时的表现。它用 json! 准备输入,用 assert_eq! 核对输出,补上了正常替换之外的容错场景。
调用图:外部调用 2 个(assert_eq!, json!)。
ensure_unique_headers_rejects_duplicates53–62 ↗
fn ensure_unique_headers_rejects_duplicates()
作用:这个测试确认 CSV 表头不能重复。比如两列都叫 path 时,程序必须报错,否则后面按字段名取值会分不清到底是哪一列。
数据流:进去的是一个表头列表,里面故意放了两个 path。测试把它交给 ensure_unique_headers,期待得到错误;如果没有报错,就用 panic! 主动让测试失败。最后它检查错误内容是不是明确写着 csv header path is duplicated。出来的是测试是否通过,以及对错误信息的验证。
调用关系:它由测试框架调用,用来保护 ensure_unique_headers 这个表头检查步骤。它用 vec! 构造重复表头,用 panic! 防止错误被漏掉,再用 assert_eq! 确认返回给上层的错误类型和提示文字都符合预期。
调用图:外部调用 3 个(assert_eq!, panic!, vec!)。
core/src/tools/handlers/apply_patch_spec_tests.rs源码 ↗
apply_patch 是一个用来改文件的工具。这个测试文件不是真的去改文件,而是检查“工具说明书”有没有生成正确。可以把它想成检查一张操作说明卡:名字要叫 apply_patch,描述要提醒“不要包成 JSON”,语法格式也要是系统约定的那套。文件里有两个测试:第一个确认默认生成的工具规格和预期完全一样;第二个确认当系统要求带上 environment_id(环境编号,用来区分在哪个运行环境里操作)时,语法定义里确实允许并包含这段内容。这样一来,如果以后有人改了 apply_patch 的说明、语法或开关行为,测试会立刻失败,提醒开发者别不小心破坏模型和工具之间的约定。
create_apply_patch_freeform_tool_matches_expected_spec5–20 ↗
fn create_apply_patch_freeform_tool_matches_expected_spec()
作用:这个测试确认:不要求环境编号时,生成出来的 apply_patch 工具说明必须和预先写好的标准版本一模一样。有人改错名称、描述或语法格式时,它会第一时间报错。
数据流:测试开始时调用 create_apply_patch_freeform_tool,并传入 false,意思是“不要包含 environment_id”。它拿到生成的工具规格后,用 assert_eq!(断言相等,也就是自动比较两个东西是否完全一样)和手写的标准规格做比较。结果要么通过,表示生成内容没变;要么失败,指出实际内容和预期哪里不同。
调用关系:它是测试运行器在跑单元测试时自动调用的检查项。它直接检查 create_apply_patch_freeform_tool 的输出,不再把工作交给别的项目函数;核心作用是守住 apply_patch 工具对外展示的默认说明格式。
调用图:外部调用 1 个(assert_eq!)。
create_apply_patch_freeform_tool_includes_environment_id_when_requested23–36 ↗
fn create_apply_patch_freeform_tool_includes_environment_id_when_requested()
作用:这个测试确认:当调用方明确要求加入环境编号时,apply_patch 的语法说明里真的包含 environment_id 相关规则。这样可以防止多环境场景下补丁不知道该发到哪里。
数据流:测试先调用 create_apply_patch_freeform_tool,并传入 true,意思是“需要包含 environment_id”。它随后确认返回的是 Freeform 工具规格;如果不是,就用 panic!(直接让测试失败)报错。接着它查看语法定义文本,使用 assert!(断言某个条件为真)检查里面是否包含 environment_id?,以及具体的“*** Environment ID: ”这一行格式。通过表示语法支持环境编号;失败表示生成规则缺了关键内容。
调用关系:它也是由测试运行器自动执行的单元测试。它关注的是 create_apply_patch_freeform_tool 的可选开关行为:默认测试看“不带环境编号”的情况,这个测试补上“带环境编号”的情况,两者合起来保证这个工具说明在两种模式下都可靠。
core/src/tools/handlers/apply_patch_tests.rs源码 ↗
这个文件不是正式功能代码,而是一组自动检查。可以把它理解成 apply_patch 工具的“验收清单”。它先准备一段最小补丁,再伪造一次工具调用,看系统在调用前、调用后交给 hook(钩子,工具执行前后插入的检查点)的数据是不是对的。接着它测试补丁内容是一点点流进来时,系统能不能及时看出正在改哪些文件,并在最后补全真正的文件内容。它还检查带环境标记的补丁、环境选择开关、移动文件时的审批路径,以及沙箱权限(限制程序能写哪些目录的安全边界)。这些测试很重要,因为 apply_patch 会直接碰磁盘文件;如果这里出错,轻则界面显示不准,重则可能绕过审批或写到不该写的位置。
sample_patch23–28 ↗
fn sample_patch() -> &'static str
作用:提供一段固定的示例补丁,用来在多个测试里复用。它模拟“新增 hello.txt,内容是 hello”这个最简单的改文件请求。
数据流:它不接收输入,也不读取外部文件;函数内部直接放着一段补丁文本;调用后返回这段文本,让测试不用每次重复写同样的字符串。
调用关系:它是测试里的小样本库。pre_tool_use_payload_uses_freeform_patch_input 和 post_tool_use_payload_uses_patch_input_and_tool_output 会先拿到这段补丁,再检查 apply_patch 的前置、后置 hook 数据是否正确。
调用图:被 2 处调用(post_tool_use_payload_uses_patch_input_and_tool_output, pre_tool_use_payload_uses_freeform_patch_input)。
invocation_for_payload30–42 ↗
async fn invocation_for_payload(payload: ToolPayload) -> ToolInvocation
作用:搭一个假的工具调用现场,让测试能像真的用户调用 apply_patch 一样检查处理器行为。它把会话、轮次、取消令牌、差异跟踪器、调用编号、工具名和输入数据装到一起。
数据流:输入是一份 ToolPayload,也就是工具收到的参数;它先创建测试用的 session 和 turn,再生成取消令牌、差异跟踪器等运行时需要的零件;最后输出一个完整的 ToolInvocation,供后面的测试拿去喂给 ApplyPatchHandler。
调用关系:它是测试准备阶段的装配工。pre_tool_use_payload_uses_freeform_patch_input 和 post_tool_use_payload_uses_patch_input_and_tool_output 都调用它,避免每个测试重复搭建一整套工具调用上下文。
调用图:调用 3 个内部函数(make_session_and_context, new, plain);被 2 处调用(post_tool_use_payload_uses_patch_input_and_tool_output, pre_tool_use_payload_uses_freeform_patch_input);外部调用 3 个(new, new, new)。
pre_tool_use_payload_uses_freeform_patch_input45–60 ↗
async fn pre_tool_use_payload_uses_freeform_patch_input()
作用:检查 apply_patch 执行之前发给 hook 的数据是否包含原始补丁文本。这样外部检查器才能在真正改文件前看到“准备改什么”。
数据流:测试先从 sample_patch 拿到补丁文本,把它放进自定义输入里,再用 invocation_for_payload 包成一次工具调用;随后创建默认的 ApplyPatchHandler,并调用它的 pre_tool_use_payload;最后断言输出必须是 apply_patch 的 hook 名称,并把补丁放在 command 字段里。
调用关系:它验证 ApplyPatchHandler 在工具执行前的对外汇报格式。它依赖 sample_patch 提供补丁,依赖 invocation_for_payload 构造现场,然后直接检查 handler 的 pre_tool_use_payload 结果。
调用图:调用 2 个内部函数(invocation_for_payload, sample_patch);外部调用 2 个(assert_eq!, default)。
post_tool_use_payload_uses_patch_input_and_tool_output63–81 ↗
async fn post_tool_use_payload_uses_patch_input_and_tool_output()
作用:检查 apply_patch 执行之后发给 hook 的数据是否同时带上“当初输入的补丁”和“工具执行结果”。这能让审计或日志知道这次调用做了什么、结果怎样。
数据流:测试先准备补丁输入,再构造一次工具调用;接着用一段成功提示做成 ApplyPatchToolOutput;然后调用 handler.post_tool_use_payload;最后确认结果里有工具名、调用编号、原始补丁,以及字符串形式的工具响应。
调用关系:它验证 ApplyPatchHandler 在工具执行后的汇报格式。它和前置 hook 测试很像,但多检查了工具输出和 tool_use_id,确保一次工具调用能被完整追踪。
调用图:调用 3 个内部函数(from_text, invocation_for_payload, sample_patch);外部调用 2 个(assert_eq!, default)。
diff_consumer_streams_apply_patch_changes84–135 ↗
fn diff_consumer_streams_apply_patch_changes()
作用:检查补丁内容分几段传进来时,差异消费者能不能边读边报告文件改动,并在结束时给出完整内容。这里的差异消费者可以理解成“边听边记的记录员”。
数据流:测试创建一个默认的 ApplyPatchArgumentDiffConsumer,然后依次喂给它补丁开头、新增文件头、内容行、结束标记;一开始信息不够时没有输出,看到新增文件后先报告 hello.txt 会被新增但内容还为空;最后完成解析时,再报告 hello.txt 的完整新增内容是 hello 和 world 两行。
调用关系:它验证 ApplyPatchArgumentDiffConsumer 的流式解析能力。工具调用参数可能不是一次性到齐的,这个测试保证上层界面或审批系统可以尽早知道哪些文件正在被改,并在最后拿到准确结果。
调用图:外部调用 3 个(assert!, assert_eq!, default)。
diff_consumer_streams_apply_patch_changes_with_environment_header138–161 ↗
fn diff_consumer_streams_apply_patch_changes_with_environment_header()
作用:检查补丁里多了环境编号这一行时,差异消费者仍然能正常识别文件改动。环境编号表示补丁想作用到哪个运行环境,但它不应该干扰文件解析。
数据流:测试先喂入补丁开头和一行 Environment ID,再喂入新增 hello.txt 的片段;消费者跳过或接受这个环境头信息后,输出一次进度事件;事件里应该说明 hello.txt 会被新增,内容暂时为空。
调用关系:它补充验证 ApplyPatchArgumentDiffConsumer 对扩展头部的兼容性。和普通流式解析测试相比,它专门覆盖带环境信息的输入,防止新增协议字段后把旧的文件差异识别弄坏。
调用图:外部调用 3 个(assert!, assert_eq!, default)。
diff_consumer_sends_next_update_after_buffer_interval164–194 ↗
fn diff_consumer_sends_next_update_after_buffer_interval()
作用:检查差异消费者不会每来一个字符都疯狂发通知,而是隔一段缓冲时间后再发下一次进度。这样既能及时更新,又不会把系统刷爆。
数据流:测试先喂入补丁开头和新增文件片段,拿到第一次进度事件;然后人为把 last_sent_at 调成“上次发送已经过了一个缓冲间隔”;再喂入新的一行内容;这时消费者应该发出第二次事件,报告目前已经确认到 hello 这一行。
调用关系:它测试 ApplyPatchArgumentDiffConsumer 的节流行为。这个消费者服务于实时进度展示,但需要用 APPLY_PATCH_ARGUMENT_DIFF_BUFFER_INTERVAL 控制发送频率,避免上层收到太多碎片更新。
调用图:外部调用 3 个(assert_eq!, default, now)。
reconcile_environment_id_requires_selection_when_enabled197–210 ↗
fn reconcile_environment_id_requires_selection_when_enabled()
作用:检查环境编号的规则是否安全:如果当前轮次不允许选择环境,却传了环境编号,就必须报错;如果允许但没传,则可以继续。
数据流:测试先把 parsed_environment_id 设成 remote,同时把 allow_environment_id 设成 false;函数应返回一个给模型看的错误,说明本轮不能选择 apply_patch 环境。接着测试没有环境编号但允许环境选择的情况,结果应为 Ok(None)。
调用关系:它直接验证 require_environment_id 这道安全门。这个逻辑会影响 apply_patch 是否能指定远端或其他环境,测试确保没有授权时不能偷偷选择环境。
调用图:外部调用 1 个(assert_eq!)。
approval_keys_include_move_destination213–242 ↗
async fn approval_keys_include_move_destination()
作用:检查移动文件的补丁在审批时不只看原文件路径,也要看目标路径。否则用户可能批准修改旧文件,却实际把内容写到另一个没审批过的位置。
数据流:测试创建临时目录,里面放一个 old/name.txt,并准备一个把它移动到 renamed/dir/name.txt 的补丁;然后调用补丁解析与验证函数得到 action;再用 file_paths_for_action 提取需要审批的路径;最后确认路径数量是 2,也就是源路径和目标路径都被算进去。
调用关系:它连接了补丁解析和审批路径收集两块逻辑。maybe_parse_apply_patch_verified 负责把补丁变成可执行动作,file_paths_for_action 负责从动作里抽取需要检查的文件路径;这个测试确保“移动到哪里”不会被漏掉。
调用图:外部调用 7 个(new, assert_eq!, maybe_parse_apply_patch_verified, panic!, create_dir_all, write, vec!)。
write_permissions_for_paths_skip_dirs_already_writable_under_workspace_root245–262 ↗
fn write_permissions_for_paths_skip_dirs_already_writable_under_workspace_root()
作用:检查如果目标文件已经在工作区根目录下面,而沙箱本来就允许写工作区,就不要再额外申请权限。这样权限请求不会变得啰嗦,也不会扩大授权范围。
数据流:测试创建一个临时工作区和 nested 子目录,把 nested/file.txt 做成绝对路径;再创建一个允许写工作区的沙箱策略;调用 write_permissions_for_paths 后,结果应该是 None,表示不需要额外写权限。
调用关系:它验证 write_permissions_for_paths 对工作区内路径的判断。这个函数通常用于 apply_patch 执行前准备沙箱权限;本测试保证普通工作区内写文件不会生成多余的权限配置。
调用图:调用 2 个内部函数(workspace_write, try_from);外部调用 3 个(new, assert_eq!, create_dir_all)。
write_permissions_for_paths_keep_dirs_outside_workspace_root265–291 ↗
fn write_permissions_for_paths_keep_dirs_outside_workspace_root()
作用:检查如果补丁要写到工作区外面的目录,权限配置必须把那个外部目录单独列出来。这样沙箱既能允许必要写入,又不会默认放开整个系统。
数据流:测试创建一个 workspace 目录和一个 outside 目录,把 outside/file.txt 做成绝对路径;沙箱策略只默认允许工作区写入,并排除临时目录的默认放行;调用 write_permissions_for_paths 后,测试取出生成的写权限列表,确认里面正好包含 outside 目录的规范化绝对路径。
调用关系:它验证 write_permissions_for_paths 对工作区外路径的处理。和上一个测试相反,这里需要额外权限;测试确保 apply_patch 在必须写外部位置时,会向沙箱明确申请那个目录,而不是悄悄失败或过度授权。
调用图:调用 2 个内部函数(workspace_write, try_from);外部调用 4 个(new, assert_eq!, simplified, create_dir_all)。
core/src/tools/handlers/mcp_resource_spec_tests.rs源码 ↗
这里测试的是三种 MCP 资源工具的规格是否固定不变。MCP 可以理解成一种让外部服务器把文件、数据库结构、业务资料等上下文交给模型看的协议;这些工具就是让模型列出资源、列出资源模板、读取某个资源。这个文件不真正去连服务器,也不读取文件,而是检查工具的“菜单卡片”写得对不对:工具叫什么、用户能传哪些参数、哪些参数必须填、参数说明是什么。它把代码实际生成的工具规格,和测试里手写的标准答案逐字比较。这样一来,如果有人改了工具名、漏了必填参数,或者把说明文字改得和接口约定不一致,测试会立刻失败。它像给公共接口贴了一把封条,防止外部调用者和模型因为规格变化而用错工具。
list_mcp_resources_tool_matches_expected_spec7–34 ↗
fn list_mcp_resources_tool_matches_expected_spec()
作用:这个测试确认“列出 MCP 资源”的工具规格完全符合预期。它主要防止工具名、说明文字、server 和 cursor 这两个参数的定义被改坏。
数据流:进去时没有外部输入;测试里先拿到当前代码生成的 list_mcp_resources 工具规格,再写出一份标准答案,包括工具名、描述、参数的 JSON Schema(JSON Schema 是一种描述 JSON 数据长什么样的规则);最后用比较断言检查两边是否一模一样。出来的结果不是业务数据,而是测试通过或失败;如果不一致,测试框架会报告差异。
调用关系:它会在运行测试时由 Rust 测试框架自动执行。它把判断工作交给 assert_eq!,也就是“必须相等”的检查;这个检查相当于守门员,发现当前生成的工具规格和手写标准不一样就拦下来。
调用图:外部调用 1 个(assert_eq!)。
list_mcp_resource_templates_tool_matches_expected_spec37–64 ↗
fn list_mcp_resource_templates_tool_matches_expected_spec()
作用:这个测试确认“列出 MCP 资源模板”的工具规格没有偏离约定。资源模板是带参数的资源入口,比如同一种资料可以按不同参数取不同内容。
数据流:进去时没有外部输入;测试获取当前生成的 list_mcp_resource_templates 工具规格,再构造一份期望中的规格,里面规定了工具名称、用途说明,以及可选的 server 和 cursor 参数;然后用断言比较两份规格。之前是代码可能被改动后的真实规格,之后是测试给出一个明确结论:完全一致就通过,有任何差别就失败。
调用关系:它也是由测试框架自动运行的接口稳定性检查。它依靠 assert_eq! 做最后裁判,用来提醒开发者:资源模板工具的公开“菜单说明”不能随便变,否则调用它的人或模型可能会按旧规则传错参数。
调用图:外部调用 1 个(assert_eq!)。
read_mcp_resource_tool_matches_expected_spec67–96 ↗
fn read_mcp_resource_tool_matches_expected_spec()
作用:这个测试确认“读取某个 MCP 资源”的工具规格正确。它特别检查 server 和 uri 这两个参数都被标成必填,因为读取资源必须知道去哪个服务器、读哪个资源地址。
数据流:进去时没有外部输入;测试拿到当前生成的 read_mcp_resource 工具规格,再写出期望结果:工具名、说明文字、两个字符串参数 server 和 uri,以及它们都是必填项;最后用 assert_eq! 比较实际值和期望值。输出表现为测试结果:一致就说明接口描述没问题,不一致就让测试失败并暴露差异。
调用关系:它在测试阶段自动运行,专门守住读取资源这个工具的接口契约。它把“是否完全一致”的判断交给 assert_eq!;如果有人把必填参数改成可选,或者改了参数含义说明,这个测试会第一时间失败。
调用图:外部调用 1 个(assert_eq!)。
core/src/tools/handlers/mcp_resource_tests.rs源码 ↗
这个文件不是真正给用户运行的功能,而是“验收清单”。它模拟一些资源、资源模板和读取资源的结果,然后把它们转成 JSON,检查里面该有的字段有没有、顺序稳不稳定、游标有没有保留下来。JSON 可以理解成系统之间传纸条用的固定格式;字段名错了,另一边就可能看不懂。这里还测了两个容易出问题的地方:空参数或 null 参数要当作“没有参数”,不能报错;大资源内容要按限制截断,避免一次返回太多文字。整体上,它像出厂前的抽检,保证资源工具对外说话的格式稳定可靠。
resource7–19 ↗
fn resource(uri: &str, name: &str) -> Resource
作用:这是测试里用的小帮手,用最少的信息造出一个资源对象。这样每个测试不用反复写一大串默认字段。
数据流:进去的是资源地址 uri 和资源名字 name → 它把这两个字符串放进一个原始资源结构里,其余可选信息都留空 → 出来的是一个没有额外标注的 Resource,供测试拿去序列化或放进列表。
调用关系:调用图里它被 resource_with_server_serializes_server_field 使用,用来准备一个假的资源样本;真正的检查工作交给后面的序列化和断言来完成。
调用图:被 1 处调用(resource_with_server_serializes_server_field)。
template21–31 ↗
fn template(uri_template: &str, name: &str) -> ResourceTemplate
作用:这是测试里用来造资源模板的小帮手。资源模板就是带占位符的资源地址样式,比如 memo://{id} 这种。
数据流:进去的是 uri_template 和 name → 它生成一个原始资源模板,把标题、描述、图标等可选字段都留空 → 出来的是一个没有额外标注的 ResourceTemplate,方便测试检查模板被输出成什么样。
调用关系:调用图里它被 template_with_server_serializes_server_field 使用,负责先搭好测试材料;后者再检查加上服务器名后的 JSON 格式。
调用图:被 1 处调用(template_with_server_serializes_server_field)。
resource_with_server_serializes_server_field34–41 ↗
fn resource_with_server_serializes_server_field()
作用:这个测试确认:一个资源被包装成“来自某个服务器的资源”后,转成 JSON 时会带上 server 字段。没有这个字段,调用方就不知道资源来自哪台服务器。
数据流:它先用 resource 造一个地址为 memo://id、名字为 memo 的资源 → 再用 ResourceWithServer::new 把服务器名 test 贴上去 → 然后转成 JSON,检查 server、uri、name 三个字段都正确。
调用关系:这是由 Rust 测试框架在跑测试时执行的检查。它调用 new 来包装资源,调用 resource 准备样本,再用 serde_json::to_value 和 assert_eq! 验证最终输出。
调用图:调用 2 个内部函数(new, resource);外部调用 2 个(assert_eq!, to_value)。
list_resources_payload_from_single_server_copies_next_cursor44–58 ↗
fn list_resources_payload_from_single_server_copies_next_cursor()
作用:这个测试确认:列出单个服务器资源时,分页用的 nextCursor 不会丢。nextCursor 可以理解成“下次从哪里继续翻页”的书签。
数据流:它先准备一个 ListResourcesResult,里面有一个资源和 cursor-1 这个下一页标记 → 调用 from_single_server 生成对外返回的 payload → 转成 JSON 后,检查 server 是 srv,nextCursor 仍是 cursor-1,并且资源条目也被标上 srv。
调用关系:测试框架运行它时,它会把一个底层资源列表交给 ListResourcesPayload::from_single_server;这个函数负责整理格式,测试再用序列化和断言确认整理结果没漏信息。
调用图:调用 1 个内部函数(from_single_server);外部调用 3 个(assert_eq!, to_value, vec!)。
list_resources_payload_from_all_servers_is_sorted61–86 ↗
fn list_resources_payload_from_all_servers_is_sorted()
作用:这个测试确认:从多个服务器收集资源后,对外输出的资源顺序是稳定的。稳定顺序很重要,因为否则同样的数据每次返回顺序不同,会让测试、缓存或用户界面都变得不可靠。
数据流:它先准备一个映射表,里面有 beta 和 alpha 两台服务器的资源 → 调用 from_all_servers 合并成一个 payload → 转成 JSON 后取出所有 uri,确认顺序是 alpha 的资源先来,再到 beta 的资源。
调用关系:测试框架运行它时,它把多服务器资源交给 ListResourcesPayload::from_all_servers;该函数负责合并和排序,测试用 assert_eq! 检查最终顺序。
调用图:调用 1 个内部函数(from_all_servers);外部调用 4 个(new, assert_eq!, to_value, vec!)。
call_tool_result_from_content_marks_success89–93 ↗
fn call_tool_result_from_content_marks_success()
作用:这个测试确认:把普通内容包装成工具调用结果时,会被标成成功而不是错误。这样上层看到结果时,不会误以为工具失败了。
数据流:进去的是一段内容字符串 {} 和一个表示成功的标记 Some(true) → 被包装成工具调用结果 → 测试检查 is_error 变成 Some(false),并且结果里确实有一条内容。
调用关系:测试框架运行它时,它围绕 call_tool_result_from_content 的结果做检查;这里主要靠 assert_eq! 验证“成功标记”和“内容数量”这两个关键结果。
调用图:外部调用 1 个(assert_eq!)。
parse_arguments_handles_empty_and_json96–111 ↗
fn parse_arguments_handles_empty_and_json()
作用:这个测试确认参数解析足够宽容:空白内容和 JSON 的 null 都表示“没有参数”,真正的 JSON 对象则能正常读出来。
数据流:它先输入只有空格和换行的字符串 → 期望得到 None;再输入 null → 也期望得到 None;最后输入 {"server":"figma"} → 期望得到一个 JSON 值,并能读到 server 等于 figma。
调用关系:测试框架运行它时,它直接检查 parse_arguments 的几种典型输入。assert! 用来确认空参数被当成没有参数,assert_eq! 用来确认普通 JSON 字段没被读错。
调用图:外部调用 2 个(assert!, assert_eq!)。
template_with_server_serializes_server_field114–126 ↗
fn template_with_server_serializes_server_field()
作用:这个测试确认:资源模板加上服务器名后,转成 JSON 时格式正确。模板如果缺少 server 字段,调用方就不知道该向哪个服务器按模板取资源。
数据流:它先用 template 造出 memo://{id} 这个模板 → 再用 ResourceTemplateWithServer::new 加上服务器名 srv → 转成 JSON 后,检查结果正好包含 server、uriTemplate 和 name 这三个字段。
调用关系:测试框架运行它时,它调用 new 和 template 准备带服务器的模板对象,再交给 serde_json::to_value 转成可检查的 JSON,最后用 assert_eq! 对比完整结果。
调用图:调用 2 个内部函数(new, template);外部调用 2 个(assert_eq!, to_value)。
serialize_function_output_preserves_small_payload129–138 ↗
fn serialize_function_output_preserves_small_payload()
作用:这个测试确认:小的输出内容不会被无端改写或截断。也就是说,内容不大时,系统应该原样返回。
数据流:它先准备一个很小的 JSON:server 是 hosted,resources 是空数组 → 用普通 JSON 序列化得到期望字符串 → 再调用 serialize_function_output,并设置 1024 字节的截断上限 → 最后确认输出文本和原始序列化结果完全一样。
调用关系:测试框架运行它时,它用 TruncationPolicy::Bytes 指定大小限制,再把 payload 交给 serialize_function_output;测试关注的是小内容路径,不应该触发截断。
调用图:外部调用 4 个(assert_eq!, json!, Bytes, to_string)。
serialize_function_output_caps_read_resource_payload141–162 ↗
fn serialize_function_output_caps_read_resource_payload()
作用:这个测试确认:读取资源得到特别大的文本时,输出会被限制在合理范围内。这样可以避免一次把超长内容塞给调用方,造成界面卡顿或消息过大。
数据流:它先设置 8000 字节的截断策略 → 构造一个读取资源结果,里面有 16000 个 x 组成的大文本 → 先算出完整序列化字符串,再算出按规则截断后的期望文本 → 调用 serialize_function_output 后,确认输出不等于完整版本,而等于截断版本。
调用关系:测试框架运行它时,它先用 ReadResourceResult::new 准备大资源,再用 serialize_function_output 走实际输出流程;最后通过 assert_ne! 和 assert_eq! 同时确认“确实截断了”和“截断方式正确”。
调用图:外部调用 6 个(new, assert_eq!, assert_ne!, Bytes, to_string, vec!)。
core/src/tools/handlers/mcp_search_tests.rs源码 ↗
这份测试文件像是在给工具搜索功能做“验收”。MCP 可以理解成一种让外部工具接入系统的协议;这里的例子是一个日历插件,能创建日程。测试先造出一份固定的工具资料,包括工具名、标题、说明、参数名、连接器名字等。然后用 McpHandler 把这份资料转换成搜索系统能用的信息。第一个测试重点看搜索文本:它应该把工具内部名、展示名、服务器名、插件名、说明、参数名都拼进去,这样用户用不同关键词都可能搜到。第二个测试看一个兜底规则:如果命名空间本身没有说明,就用连接器名字生成一句默认说明,比如“用于处理 Calendar 的工具”。这些测试重要的地方在于,它们不是测算法快不快,而是防止搜索体验被细小改动悄悄弄坏。
search_info_uses_mcp_tool_metadata_and_parameter_names8–23 ↗
fn search_info_uses_mcp_tool_metadata_and_parameter_names()
作用:这个测试确认 MCP 工具生成的搜索信息足够完整。它要保证工具名、标题、说明、插件名、参数名等都会进入搜索文本,让用户更容易搜到这个工具。
数据流:进去的是 tool_info 造出来的一份假日历工具资料 → 测试把它交给 McpHandler::new 生成处理器,再调用 search_info 得到搜索用的数据 → 最后检查出来的 search_text 和 source_info 是否和预先写死的正确结果完全一样;它不改动真实系统,只验证结果。
调用关系:它是测试入口之一,运行测试时由 Rust 测试框架调用。它先请 tool_info 准备固定样本,再把样本交给 McpHandler 构建搜索信息,最后用 assert_eq! 做严格比对;如果搜索信息少了参数名或说明,这个测试就会失败。
调用图:调用 2 个内部函数(new, tool_info);外部调用 1 个(assert_eq!)。
search_info_uses_connector_name_for_output_namespace_description26–43 ↗
fn search_info_uses_connector_name_for_output_namespace_description()
作用:这个测试确认当工具命名空间没有自己的说明时,系统会用连接器名字生成一个默认说明。这样界面上不会出现空白或让人看不懂的工具分类描述。
数据流:进去的是 tool_info 生成的假工具资料 → 测试先故意把 namespace_description 清空 → 再创建 McpHandler 并取出 search_info → 然后检查输出确实是一个命名空间,并且说明变成了“Tools for working with Calendar.”;同时也检查来源信息里只保留连接器名字,没有描述。
调用关系:它也是由测试框架直接运行的测试。它依赖 tool_info 提供基础样本,然后专门改掉其中一个字段来模拟缺少说明的情况;如果输出类型不是命名空间,它会立刻 panic!,因为后面的检查就没有意义了。
调用图:调用 2 个内部函数(new, tool_info);外部调用 2 个(assert_eq!, panic!)。
tool_info45–70 ↗
fn tool_info() -> ToolInfo
作用:这个辅助函数专门造一份稳定的假 MCP 工具资料,供上面的测试反复使用。它相当于测试里的“样品日历工具”。
数据流:进去没有外部参数 → 它在函数里写死服务器名、工具名、命名空间、说明、连接器名、插件显示名,以及 JSON 参数结构,比如 start_time 和 attendees → 出来的是一个 ToolInfo 对象,测试拿它来创建 McpHandler。
调用关系:它不直接做断言,而是给两个测试准备同一套基础数据。search_info_uses_mcp_tool_metadata_and_parameter_names 用它检查完整元数据进入搜索文本;search_info_uses_connector_name_for_output_namespace_description 也用它,但会先改掉命名空间说明来测试兜底行为。
调用图:被 2 处调用(search_info_uses_connector_name_for_output_namespace_description, search_info_uses_mcp_tool_metadata_and_parameter_names);外部调用 5 个(new, json!, new, object, vec!)。
core/src/tools/handlers/multi_agents_spec_tests.rs源码 ↗
这个文件不是真正运行多代理功能的地方,而是专门验收工具“长什么样”。这里的工具包括创建新代理、给代理发消息、追加任务、等待代理、列出代理等。测试会把工具生成出来,然后检查它们的 JSON Schema(可以理解成“参数表格和填写规则”):哪些字段必须填,哪些字段要加密,哪些老字段还要保留,输出里应该有哪些字段。它还检查模型列表的展示规则,比如只展示可见模型,最多展示前几个,过长的 reasoning effort(推理强度文字)要截断。这样做很重要,因为这些工具描述是给 AI 模型看的;一旦字段名、必填项或说明文字错了,模型就可能调用失败,或者把不该暴露的内容写进参数里。
model_preset11–37 ↗
fn model_preset(id: &str, show_in_picker: bool) -> ModelPreset
作用:造一个假的模型配置,方便测试时不用真的去读线上模型列表。它让每个测试都能快速准备“可见模型”或“隐藏模型”。
数据流:进去的是一个模型编号和一个“是否在选择器里显示”的开关;函数把这些拼成一个完整的 ModelPreset,包括模型名、展示名、描述、默认推理强度和服务档位;出来的是一份可直接交给工具生成器使用的假模型资料。
调用关系:它是测试里的小道具。spawn_agent_tool_caps_reasoning_effort_value_length 会先用它造出基础模型,再把推理强度改成长字符串来测试截断规则;函数内部主要用字符串创建、格式化和列表创建这些基础动作。
调用图:被 1 处调用(spawn_agent_tool_caps_reasoning_effort_value_length);外部调用 3 个(new, format!, vec!)。
spawn_agent_tool_v2_requires_task_name_and_lists_visible_models40–116 ↗
fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models()
作用:检查新版创建代理工具必须要求任务名和消息,并且只把“可见”的模型写进工具说明里。这样可以避免模型调用工具时少填关键字段,也避免隐藏模型被展示出来。
数据流:测试先准备一个可见模型和一个隐藏模型,再生成新版 spawn_agent 工具;随后拆开工具说明和参数表,逐项检查:参数必须是对象,必须有 task_name 和 message,消息字段要加密,旧字段不能出现,可见模型说明要出现,隐藏模型不能出现;最后还检查输出格式里必须有任务名和昵称。
调用关系:这是对新版 spawn_agent 工具规格的主验收。它把生成出的工具当成成品说明书来查,发现字段或文字不符合预期就用断言让测试失败;如果工具类型不对,会直接触发 panic,说明生成器产物已经严重偏离预期。
调用图:外部调用 4 个(assert!, assert_eq!, panic!, vec!)。
spawn_agent_tool_v1_keeps_legacy_fork_context_field119–166 ↗
fn spawn_agent_tool_v1_keeps_legacy_fork_context_field()
作用:检查旧版创建代理工具仍然保留老字段 fork_context。这是为了兼容已经按旧格式调用工具的使用方,不让升级突然把它们弄坏。
数据流:测试生成 v1 版本的 spawn_agent 工具;然后确认它被包在旧版命名空间里,再取出里面的函数工具;接着检查参数是对象,里面有 fork_context、没有新版的 fork_turns,消息字段不加密,并且模型和服务档位的说明仍然存在。
调用关系:它站在“老用户不能被抛下”的角度检查兼容性。测试过程中会用断言比较字段和说明,如果工具不是预期的命名空间结构,就用 panic 立刻报错。
调用图:外部调用 4 个(new, assert!, assert_eq!, panic!)。
spawn_agent_tool_caps_visible_model_summaries169–196 ↗
spawn_agent_tool_caps_reasoning_effort_value_length199–217 ↗
fn spawn_agent_tool_caps_reasoning_effort_value_length()
作用:检查模型说明里的推理强度文字太长时会被截短。这样可以防止一段异常长的模型配置把工具说明撑爆。
数据流:测试先用 model_preset 造一个可见模型,再把它的推理强度改成超过上限的长字符串;然后调用模型说明生成逻辑,期望结果只保留允许长度内的文字;最后用相等断言确认输出完全符合预期。
调用关系:它复用了 model_preset 提供的基础假模型,然后只改动自己关心的字段。这个测试守住的是展示层的安全边界:模型配置可以很长,但写给模型看的工具说明不能失控。
调用图:调用 1 个内部函数(model_preset);外部调用 3 个(assert_eq!, Custom, vec!)。
spawn_agent_tool_hides_service_tier_with_spawn_metadata220–248 ↗
fn spawn_agent_tool_hides_service_tier_with_spawn_metadata()
作用:检查在要求隐藏代理类型、模型、推理强度和服务档位信息时,创建代理工具真的不会把这些字段和说明暴露出来。这个测试保护的是“不要给模型看不该看的选择项”。
数据流:测试生成一个带可见模型的新版 spawn_agent 工具,但打开隐藏相关元数据的开关;然后检查参数表里没有 agent_type、model、reasoning_effort、service_tier,说明文字里也没有继承模型提示和可用模型列表。
调用关系:它测试的是同一个工具在特殊配置下的“收起模式”。如果工具生成器以后误把这些字段加回来,断言会失败,提醒开发者这里的隐藏承诺被破坏了。
send_message_tool_requires_message_and_has_no_output_schema251–289 ↗
fn send_message_tool_requires_message_and_has_no_output_schema()
作用:检查给代理发消息的工具必须填写目标和消息,而且不声明额外输出格式。这样调用者知道这只是“把话送过去”,不是用来拿结果的。
数据流:测试生成 send_message 工具;拆开参数表后确认它是对象,包含 target 和 message,消息需要加密,不包含旧的或不该有的字段;再确认 target 的说明文字正确,必填项正好是目标和消息,输出格式为空。
调用关系:它守住发消息工具的基本契约:输入要清楚,输出不要乱承诺。函数内部主要靠断言逐项验收,如果生成出来的不是函数工具,就用 panic 表示规格完全不对。
调用图:外部调用 3 个(assert!, assert_eq!, panic!)。
followup_task_tool_requires_message_and_has_no_output_schema292–330 ↗
fn followup_task_tool_requires_message_and_has_no_output_schema()
作用:检查追加任务工具的名称、说明、必填参数和输出规则。它确保给已有非根代理追加任务时,模型知道必须提供目标和任务内容。
数据流:测试生成 followup_task 工具;确认工具名和说明文字是指定内容;再检查参数是对象,包含 target 和加密的 message,没有 items,必填项是目标和消息;最后确认没有输出格式。
调用关系:它是 followup_task 工具的规格锁。这个测试会在工具说明、参数名或输出承诺被意外改动时失败,帮助保持模型调用方式稳定。
调用图:外部调用 3 个(assert!, assert_eq!, panic!)。
wait_agent_tool_v2_uses_timeout_only_summary_output333–371 ↗
fn wait_agent_tool_v2_uses_timeout_only_summary_output()
作用:检查新版等待代理工具只接受超时时间,并且只返回简短摘要,不直接返回代理的完整内容。这样可以避免等待工具变成偷偷取内容的通道。
数据流:测试用默认、最小、最大超时时间生成 wait_agent 工具;随后确认参数表里没有目标列表,只有可选的 timeout_ms;说明文字要明确说只返回更新摘要;超时字段说明要写清默认值和范围;输出格式里的 message 也必须说明是简短等待摘要。
调用关系:它保护 wait_agent v2 的设计边界:这个工具只负责等和提示有没有更新,不负责传回正文。断言会逐项检查输入限制和输出说明,防止以后行为被误改。
调用图:外部调用 3 个(assert!, assert_eq!, panic!)。
list_agents_tool_includes_path_prefix_and_agent_fields374–402 ↗
fn list_agents_tool_includes_path_prefix_and_agent_fields()
作用:检查列出代理工具支持按任务路径前缀过滤,并且输出的每个代理至少包含关键字段。这样调用者可以筛选某一支任务树下的代理,也能拿到基本状态信息。
数据流:测试生成 list_agents 工具;检查参数是对象,里面有 path_prefix,且说明告诉用户不要带结尾斜杠、不填就是列出全部活跃代理;然后检查输出格式里每个代理条目必须有代理名、代理状态和最后一条任务消息。
调用关系:它验证 list_agents 工具对外提供的“目录查询”能力。测试通过断言确保过滤输入和输出字段都稳定,避免界面或模型依赖这些字段时突然失效。
调用图:外部调用 3 个(assert!, assert_eq!, panic!)。
list_agents_tool_status_schema_includes_interrupted405–422 ↗
fn list_agents_tool_status_schema_includes_interrupted()
作用:检查列出代理工具的状态枚举里包含 interrupted,也就是“被中断”的状态。这样调用者不会把中断中的代理误当成未知状态。
数据流:测试生成 list_agents 工具,取出输出格式里代理状态的可选值列表;然后确认列表正好包含 pending_init、running、interrupted、shutdown、not_found 这些状态。
调用关系:它补充检查代理状态这块的细节。相比上一个测试看字段是否存在,这个测试更关心状态值是否完整;如果以后有人忘了把 interrupted 放进 schema,断言会立刻发现。
调用图:外部调用 2 个(assert_eq!, panic!)。
core/src/tools/handlers/multi_agents_tests.rs源码 ↗
这个文件不是真正给用户运行的功能代码,而是测试代码。它模拟一个主代理把任务分给子代理,再检查各种边界情况:空消息要拒绝,错误的代理编号要报清楚,子代理继承模型、审批策略和沙箱权限时不能丢设置。这里还覆盖了新版多代理模式,也就是用类似“/root/worker”这样的路径来找代理,而不是只靠一串内部编号。测试会造临时会话、临时配置、假线程管理器,然后调用真实的工具处理器,看输出 JSON、状态变化、发送到线程的操作是否符合预期。没有这些测试,多代理功能很容易出现隐蔽问题,比如代理误发给自己、关闭父代理时子树状态错乱、等待时泄露子代理的敏感内容,或者恢复已关闭代理后历史关系丢失。
invocation69–85 ↗
fn invocation(
session: Arc<crate::session::session::Session>,
turn: Arc<TurnContext>,
tool_name: &str,
payload: ToolPayload,
) -> ToolInvocation
作用:帮测试快速拼出一次“工具调用”。测试不用每次手写一大坨会话、轮次、工具名和参数。
数据流:输入会话、当前轮次、工具名和参数 → 填进一个 ToolInvocation,并补上取消令牌、差异跟踪器、调用编号等默认测试值 → 返回一个可以直接交给工具处理器的调用对象。
调用关系:大量测试先用它把场景包装成一次真实工具调用,再交给 SpawnAgentHandler、SendMessageHandler、WaitAgentHandler 等处理器。它本身只搭台,不判断业务对错。
调用图:调用 2 个内部函数(default, plain);被 76 处调用(close_agent_submits_shutdown_and_returns_previous_status, handler_rejects_non_function_payloads, multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn, multi_agent_v2_followup_task_rejects_legacy_items_field, multi_agent_v2_followup_task_rejects_root_target_from_child, multi_agent_v2_full_history_fork_accepts_explicit_service_tier, multi_agent_v2_interrupt_agent_accepts_task_name_target, multi_agent_v2_interrupt_agent_accepts_unloaded_task_name_target, multi_agent_v2_interrupt_agent_rejects_root_target_and_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_id (+15 more));外部调用 3 个(new, new, new)。
function_payload87–91 ↗
fn function_payload(args: serde_json::Value) -> ToolPayload
作用:把 JSON 参数包装成函数工具能接受的参数格式。这样测试写参数时可以直接写 JSON。
数据流:输入一份 serde_json::Value → 转成字符串 → 放进 ToolPayload::Function 里返回。
调用关系:几乎所有测试都会先用它准备工具参数,再通过 invocation 组成完整调用。它保证测试走的是“函数调用”这条真实路径。
调用图:被 75 处调用(close_agent_submits_shutdown_and_returns_previous_status, multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn, multi_agent_v2_followup_task_rejects_legacy_items_field, multi_agent_v2_followup_task_rejects_root_target_from_child, multi_agent_v2_full_history_fork_accepts_explicit_service_tier, multi_agent_v2_interrupt_agent_accepts_task_name_target, multi_agent_v2_interrupt_agent_accepts_unloaded_task_name_target, multi_agent_v2_interrupt_agent_rejects_root_target_and_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name (+15 more));外部调用 1 个(to_string)。
parse_agent_id93–95 ↗
fn parse_agent_id(id: &str) -> ThreadId
作用:把文本形式的代理编号转成内部 ThreadId。测试用它确认处理器返回的代理编号确实可用。
数据流:输入一个字符串编号 → 调 ThreadId::from_string 解析 → 成功就返回 ThreadId,失败说明测试数据不该这样并直接报错。
调用关系:创建代理成功后,相关测试会用它把返回的 agent_id 变成可查询的线程编号,再去线程管理器里检查配置快照或状态。
调用图:调用 1 个内部函数(from_string);被 7 处调用(spawn_agent_full_history_fork_accepts_explicit_service_tier, spawn_agent_reapplies_runtime_sandbox_after_role_config, spawn_agent_role_service_tier_falls_back_to_supported_parent_tier, spawn_agent_service_tier_inheritance_preserves_supported_or_configured_tiers, spawn_agent_service_tier_override_validates_the_effective_child_model, spawn_agent_uses_explorer_role_and_preserves_approval_policy, tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed)。
thread_manager97–102 ↗
fn thread_manager() -> ThreadManager
作用:创建一个测试专用的线程管理器。它像测试里的“代理总管”,不用真实登录也能启动和记录代理线程。
数据流:不接收业务输入 → 用 dummy API key 和内置 OpenAI 提供商配置创建 ThreadManager → 返回可用于测试的管理器。
调用关系:多数需要真实启动、查询或捕获代理操作的测试都会先调用它,然后把它的 agent_control 挂到 session.services 上。
调用图:调用 2 个内部函数(with_models_provider_for_tests, from_api_key);被 56 处调用(close_agent_submits_shutdown_and_returns_previous_status, multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn, multi_agent_v2_followup_task_rejects_legacy_items_field, multi_agent_v2_followup_task_rejects_root_target_from_child, multi_agent_v2_full_history_fork_accepts_explicit_service_tier, multi_agent_v2_interrupt_agent_accepts_task_name_target, multi_agent_v2_interrupt_agent_rejects_root_target_and_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name, multi_agent_v2_interrupted_turn_does_not_notify_parent (+15 more));外部调用 1 个(built_in_model_providers)。
install_role_with_model_override104–136 ↗
async fn install_role_with_model_override(turn: &mut TurnContext) -> String
作用:给当前测试临时安装一个会覆盖模型设置的代理角色。用于检查角色配置会不会正确影响子代理。
数据流:输入可修改的 TurnContext → 在临时 codex_home 写一个角色配置文件 → 把角色登记进 turn.config → 返回角色名。
调用关系:和 fork 上下文、角色覆盖相关的测试会调用它。之后 spawn_agent 用这个角色名创建代理,测试再检查该允许或拒绝的覆盖行为。
调用图:被 3 处调用(multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override, multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override, spawn_agent_fork_context_rejects_agent_type_override);外部调用 3 个(new, create_dir_all, write)。
set_turn_config138–141 ↗
fn set_turn_config(turn: &mut TurnContext, config: crate::config::Config)
作用:替换当前轮次的配置,并同步刷新多代理版本。避免测试只改了配置却忘了让轮次识别新版功能。
数据流:输入 TurnContext 和新 Config → 从配置特性计算 multi_agent_version → 把配置放进 Arc 共享指针里替换到 turn 上。
调用关系:开启 MultiAgentV2 或调整等待超时等配置时会用它。它让后续处理器按新配置运行。
调用图:被 39 处调用(multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn, multi_agent_v2_followup_task_rejects_legacy_items_field, multi_agent_v2_followup_task_rejects_root_target_from_child, multi_agent_v2_full_history_fork_accepts_explicit_service_tier, multi_agent_v2_interrupt_agent_accepts_task_name_target, multi_agent_v2_interrupt_agent_accepts_unloaded_task_name_target, multi_agent_v2_interrupt_agent_rejects_root_target_and_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_id, multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name, multi_agent_v2_interrupted_turn_does_not_notify_parent (+15 more));外部调用 2 个(new, multi_agent_version_from_features)。
expect_text_output143–167 ↗
fn expect_text_output(output: T) -> (String, Option<bool>)
作用:从工具输出里取出文本内容和成功标记。测试不用关心底层输出格式有几种包装方式。
数据流:输入一个实现 ToolOutput 的结果 → 转成响应项 → 如果是函数输出就抽出文本或把内容项合成文本 → 返回文本和 success;如果不是函数输出就让测试失败。
调用关系:处理器调用成功后,大量测试用它读 JSON 字符串,再反序列化检查字段。它是测试断言前的统一拆包器。
调用图:调用 1 个内部函数(function_call_output_content_items_to_text);被 35 处调用(close_agent_submits_shutdown_and_returns_previous_status, multi_agent_v2_full_history_fork_accepts_explicit_service_tier, multi_agent_v2_interrupt_agent_accepts_task_name_target, multi_agent_v2_interrupt_agent_accepts_unloaded_task_name_target, multi_agent_v2_list_agents_filters_by_relative_path_prefix, multi_agent_v2_list_agents_keeps_interrupted_resident_agents, multi_agent_v2_list_agents_omits_closed_agents, multi_agent_v2_list_agents_returns_completed_status_without_encrypted_spawn_preview, multi_agent_v2_spawn_agent_ignores_configured_max_depth, multi_agent_v2_spawn_omits_agent_id_when_named (+15 more));外部调用 2 个(to_response_item, panic!)。
handler_rejects_non_function_payloads187–206 ↗
async fn handler_rejects_non_function_payloads()
作用:确认多代理工具不会接受非函数形式的输入。这样模型或调用方传错格式时,会得到明确错误。
数据流:创建测试会话 → 构造 Custom payload → 调 spawn_agent → 期望返回“unsupported payload”错误。
调用关系:它直接调用 invocation 和 SpawnAgentHandler,验证处理器入口处的格式防线。
调用图:调用 2 个内部函数(make_session_and_context, invocation);外部调用 4 个(new, default, assert_eq!, panic!)。
spawn_agent_rejects_empty_message209–224 ↗
async fn spawn_agent_rejects_empty_message()
作用:确认创建代理时不能发送空白任务。没有这个限制,系统可能启动一个不知道要干什么的子代理。
数据流:输入只有空格的 message → 调 spawn_agent → 处理器拒绝并返回“空消息不能发送”。
调用关系:它通过 function_payload 和 invocation 走真实调用路径,检查 SpawnAgentHandler 的参数校验。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
spawn_agent_rejects_when_message_and_items_are_both_set227–247 ↗
async fn spawn_agent_rejects_when_message_and_items_are_both_set()
作用:确认创建代理时不能同时给普通 message 和结构化 items。两套输入同时出现会让系统不知道以哪个为准。
数据流:构造同时含 message 和 items 的参数 → 调 spawn_agent → 期望得到“二选一”的错误。
调用关系:它验证旧版 spawn_agent 的输入规则,防止后续消息组装出现歧义。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
spawn_agent_uses_explorer_role_and_preserves_approval_policy250–307 ↗
async fn spawn_agent_uses_explorer_role_and_preserves_approval_policy()
作用:检查用 explorer 角色创建子代理时,父级的审批策略和模型提供商不会丢。
数据流:设置父轮次使用 ollama 和按需审批 → 创建 explorer 子代理 → 解析返回 agent_id → 查询子代理配置快照 → 断言审批策略和模型提供商保持正确。
调用关系:它把 ThreadManager 接进 session,再调用 SpawnAgentHandler,最后通过 parse_agent_id 和管理器检查真实子线程配置。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, parse_agent_id, thread_manager);外部调用 8 个(new, default, assert!, assert_eq!, create_model_provider, built_in_model_providers, json!, from_str)。
spawn_agent_fork_context_rejects_agent_type_override310–341 ↗
async fn spawn_agent_fork_context_rejects_agent_type_override()
作用:确认“完整继承历史”的 fork_context 模式不允许再指定子代理角色。因为完整 fork 应该继承父代理身份和模型。
数据流:安装一个带模型覆盖的角色 → 启动根线程 → 用 fork_context=true 且指定 agent_type 调 spawn_agent → 期望被拒绝。
调用关系:它调用 install_role_with_model_override 制造冲突配置,验证 SpawnAgentHandler 对完整历史分叉的保护。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, install_role_with_model_override, invocation, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
spawn_agent_fork_context_rejects_child_model_overrides344–376 ↗
async fn spawn_agent_fork_context_rejects_child_model_overrides()
作用:确认完整历史 fork 时不能给子代理单独改模型或推理强度。这样可以保证 fork 出来的代理和父代理上下文一致。
数据流:启动根线程 → 用 fork_context=true 并传 model、reasoning_effort → 调 spawn_agent → 期望返回要求省略覆盖项的错误。
调用关系:它直接测试 SpawnAgentHandler 的 fork_context 参数规则,和角色覆盖拒绝测试互相补充。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override379–422 ↗
async fn multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override()
作用:检查新版多代理里 fork_turns=all 也不允许指定不同角色。新版名字不同,但完整历史 fork 的原则一样。
数据流:启用 MultiAgentV2 → 安装带模型覆盖的角色 → 用 fork_turns=all 和 agent_type 调新版 spawn_agent → 期望同样被拒绝。
调用关系:它用 SpawnAgentHandlerV2 验证新版路径,和旧版 fork_context 的测试保持行为一致。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, install_role_with_model_override, invocation, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
multi_agent_v2_spawn_defaults_to_full_fork_and_rejects_child_model_overrides425–463 ↗
async fn multi_agent_v2_spawn_defaults_to_full_fork_and_rejects_child_model_overrides()
作用:确认新版 spawn_agent 默认就是完整历史 fork,因此默认情况下也不能改子模型。
数据流:启用 MultiAgentV2 → 不传 fork_turns,但传 model 和 reasoning_effort → 调新版 spawn_agent → 期望被拒绝。
调用关系:它通过 set_turn_config 切到新版,再测试 SpawnAgentHandlerV2 的默认行为。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
spawn_agent_service_tier_override_validates_the_effective_child_model466–562 ↗
async fn spawn_agent_service_tier_override_validates_the_effective_child_model()
作用:检查创建子代理时指定服务档位,必须和最终使用的模型匹配。服务档位可以理解为模型服务速度或优先级的套餐。
数据流:分别构造支持和不支持 service_tier 的模型组合 → 调 spawn_agent → 支持时创建成功并写入快照,不支持时返回清楚错误。
调用关系:它调用 SpawnAgentHandler 后用 expect_text_output 和 parse_agent_id 检查成功路径,也检查错误路径的提示。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, parse_agent_id, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
spawn_agent_service_tier_inheritance_preserves_supported_or_configured_tiers565–718 ↗
async fn spawn_agent_service_tier_inheritance_preserves_supported_or_configured_tiers()
作用:检查子代理继承父代理服务档位时,支持就保留,不支持就清掉,角色里配置的档位也要按规则处理。
数据流:设置父模型和 service_tier → 分别创建默认子代理、换模型子代理、带角色配置子代理 → 查询子线程快照 → 验证 service_tier 是否保留或清除。
调用关系:它综合测试 SpawnAgentHandler、角色配置文件和模型能力判断,确保继承逻辑不会盲目复制无效档位。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, parse_agent_id, thread_manager);外部调用 7 个(new, default, assert_eq!, json!, from_str, create_dir_all, write)。
spawn_agent_role_service_tier_falls_back_to_supported_parent_tier721–790 ↗
async fn spawn_agent_role_service_tier_falls_back_to_supported_parent_tier()
作用:确认角色文件里写了不支持的服务档位时,可以退回父代理支持的档位。这样不会因为角色配置不完美就直接失败。
数据流:父配置里放支持的 priority 档位 → 角色文件里写不支持的 turbo → 创建该角色子代理 → 检查最终快照使用父级支持的档位。
调用关系:它给 SpawnAgentHandler 提供一个故意有问题的角色配置,验证其容错和回退策略。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, parse_agent_id, thread_manager);外部调用 7 个(new, default, assert_eq!, json!, from_str, create_dir_all, write)。
spawn_agent_role_service_tier_does_not_hide_invalid_spawn_request793–840 ↗
async fn spawn_agent_role_service_tier_does_not_hide_invalid_spawn_request()
作用:确认如果调用时自己传了无效 service_tier,角色配置不能把这个错误掩盖掉。
数据流:角色文件里写合法档位 → 调用 spawn_agent 时显式传 turbo → 处理器仍然拒绝 turbo,并说明模型支持哪些档位。
调用关系:它保证 SpawnAgentHandler 优先尊重并校验用户这次请求,而不是悄悄用角色配置替换错误输入。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 6 个(new, default, assert_eq!, json!, create_dir_all, write)。
spawn_agent_full_history_fork_accepts_explicit_service_tier843–888 ↗
async fn spawn_agent_full_history_fork_accepts_explicit_service_tier()
作用:确认完整历史 fork 虽然不能改模型和角色,但可以显式指定服务档位。
数据流:父轮次使用支持档位的模型 → fork_context=true 并传 service_tier → 创建子代理 → 检查子配置快照里有该档位。
调用关系:它验证 SpawnAgentHandler 对 fork 限制的边界:禁止模型身份覆盖,但允许服务档位参数。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, parse_agent_id, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
multi_agent_v2_full_history_fork_accepts_explicit_service_tier891–954 ↗
async fn multi_agent_v2_full_history_fork_accepts_explicit_service_tier()
作用:确认新版完整历史 fork 同样允许显式服务档位。
数据流:启用 MultiAgentV2 和支持档位的模型 → 调新版 spawn_agent 传 task_name 和 service_tier → 用任务名解析子线程 → 检查配置快照。
调用关系:它用 SpawnAgentHandlerV2 和 resolve_agent_reference 检查新版路径下的相同行为。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override957–1012 ↗
async fn multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override()
作用:确认新版只 fork 部分轮次时,可以给子代理指定不同角色。因为这不是完整复制父代理历史。
数据流:安装带模型覆盖的角色 → 启用 MultiAgentV2 → 用 fork_turns="1" 和 agent_type 创建子代理 → 查询子线程快照 → 验证模型、提供商和推理强度来自角色。
调用关系:它和完整 fork 拒绝覆盖的测试形成对照,说明 SpawnAgentHandlerV2 对不同 fork_turns 有不同规则。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, install_role_with_model_override, invocation, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
spawn_agent_returns_agent_id_without_task_name1015–1039 ↗
async fn spawn_agent_returns_agent_id_without_task_name()
作用:检查旧版 spawn_agent 返回内部 agent_id,而不是新版 task_name。这样旧接口兼容老调用方。
数据流:调用旧版 spawn_agent → 读取输出 JSON → 确认有 agent_id 和 nickname,没有 task_name,并且成功标记为 true。
调用关系:它验证 SpawnAgentHandler 的返回格式,和新版 omits_agent_id 测试形成版本差异。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager);外部调用 6 个(new, default, assert!, assert_eq!, json!, from_str)。
multi_agent_v2_spawn_requires_task_name1042–1073 ↗
async fn multi_agent_v2_spawn_requires_task_name()
作用:确认新版创建代理必须给任务名。新版靠路径命名代理,没有名字就无法稳定引用。
数据流:启用 MultiAgentV2 → 调 spawn_agent 但不传 task_name → 期望解析参数时报缺少字段。
调用关系:它检查 SpawnAgentHandlerV2 的输入 schema,也说明新版不再只依赖内部 agent_id。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert!, json!, panic!)。
multi_agent_v2_spawn_rejects_legacy_items_field1076–1109 ↗
async fn multi_agent_v2_spawn_rejects_legacy_items_field()
作用:确认新版 spawn_agent 不接受旧版 items 字段。新版只允许明确的 message,避免混用协议。
数据流:启用 MultiAgentV2 → 参数里同时放 message、items 和 task_name → 调新版 spawn_agent → 期望报 unknown field items。
调用关系:它测试 SpawnAgentHandlerV2 的严格参数解析,防止旧协议悄悄进入新版逻辑。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert!, json!, panic!)。
spawn_agent_errors_when_manager_dropped1112–1127 ↗
async fn spawn_agent_errors_when_manager_dropped()
作用:确认没有代理管理器时,创建代理会给出清楚错误。否则系统可能表现成无响应。
数据流:创建没有接入 ThreadManager 的会话 → 调 spawn_agent → 得到 collab manager unavailable 错误。
调用关系:它验证 SpawnAgentHandler 对基础服务缺失的处理。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_path1130–1228 ↗
async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_path()
作用:检查新版创建代理会返回完整路径,并且之后可以用相对路径给它发消息。
数据流:启用 MultiAgentV2 → 创建 task_name=test_process 的代理 → 确认返回 /root/test_process → 再用 target=test_process 发消息 → 检查记录到子线程的通信内容和路径正确。
调用关系:它串起 SpawnAgentHandlerV2 和 SendMessageHandlerV2,验证“命名创建”和“按名字发送”能配合工作。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 6 个(new, default, assert!, assert_eq!, json!, from_str)。
multi_agent_v2_spawn_rejects_legacy_fork_context1231–1268 ↗
async fn multi_agent_v2_spawn_rejects_legacy_fork_context()
作用:确认新版不接受旧参数 fork_context。新版应该使用 fork_turns 表达 fork 范围。
数据流:启用 MultiAgentV2 → 调 spawn_agent 传 fork_context=true → 期望返回提示改用 fork_turns。
调用关系:它保护 SpawnAgentHandlerV2 的新协议边界,防止旧字段造成误解。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
multi_agent_v2_spawn_rejects_invalid_fork_turns_string1271–1308 ↗
async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string()
作用:确认 fork_turns 只能是 none、all 或正整数字符串。随便写字符串会被拒绝。
数据流:启用 MultiAgentV2 → fork_turns 传 banana → 调 spawn_agent → 返回合法取值说明。
调用关系:它测试 SpawnAgentHandlerV2 对 fork_turns 的格式校验。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
multi_agent_v2_spawn_rejects_zero_fork_turns1311–1348 ↗
async fn multi_agent_v2_spawn_rejects_zero_fork_turns()
作用:确认 fork_turns 不能写 0。要么不 fork,用 none;要么 fork 正数轮。
数据流:启用 MultiAgentV2 → fork_turns 传 "0" → 调 spawn_agent → 被同一条规则拒绝。
调用关系:它补充验证 SpawnAgentHandlerV2 对数字边界的处理。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 4 个(new, default, assert_eq!, json!)。
multi_agent_v2_send_message_accepts_root_target_from_child1351–1425 ↗
async fn multi_agent_v2_send_message_accepts_root_target_from_child()
作用:确认子代理可以向根代理发普通消息。根代理像项目里的总协调者,子代理需要能汇报结果。
数据流:创建根线程和一个子线程,并把当前会话切到子代理身份 → 调 send_message target=/root → 检查根线程收到来自子路径的通信。
调用关系:它直接测试 SendMessageHandlerV2 对根路径目标的允许规则。
调用图:调用 6 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager, try_from);外部调用 6 个(new, SubAgent, assert!, default, json!, vec!)。
multi_agent_v2_followup_task_rejects_root_target_from_child1428–1508 ↗
async fn multi_agent_v2_followup_task_rejects_root_target_from_child()
作用:确认 followup_task 不能把根代理当目标。后续任务应该派给子代理,不该让子代理反过来驱动根代理。
数据流:把当前身份设成子代理 → 调 followup_task target=/root → 返回拒绝错误 → 检查根线程没有收到中断或通信操作。
调用关系:它验证 FollowupTaskHandlerV2 的安全边界,和 send_message 可发根代理形成区别。
调用图:调用 6 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager, try_from);外部调用 8 个(new, SubAgent, assert!, assert_eq!, default, json!, panic!, vec!)。
multi_agent_v2_list_agents_returns_completed_status_without_encrypted_spawn_preview1511–1599 ↗
async fn multi_agent_v2_list_agents_returns_completed_status_without_encrypted_spawn_preview()
作用:检查新版列出代理时,会显示完成状态,但不会把创建时的加密消息当预览泄露出来。
数据流:创建 worker → 模拟 worker 完成并留下 last_agent_message → 调 list_agents → 结果包含 /root 和 /root/worker,worker 状态为 completed,任务预览为空。
调用关系:它测试 ListAgentsHandlerV2 对代理状态展示和敏感内容隐藏的处理。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 6 个(new, default, assert_eq!, json!, TurnComplete, from_str)。
multi_agent_v2_list_agents_filters_by_relative_path_prefix1602–1686 ↗
async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix()
作用:确认列代理时可以按相对路径前缀筛选。子代理只想看自己下面某个分支时会用到。
数据流:创建 /root/researcher 和 /root/researcher/worker → 当前身份切到 researcher → 用 path_prefix=worker 调 list_agents → 只返回 worker。
调用关系:它验证 ListAgentsHandlerV2 的路径解析和过滤规则。
调用图:调用 7 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, from_string);外部调用 7 个(new, SubAgent, assert_eq!, default, json!, from_str, vec!)。
multi_agent_v2_list_agents_omits_closed_agents1689–1750 ↗
async fn multi_agent_v2_list_agents_omits_closed_agents()
作用:确认已经关闭的代理不会出现在新版列表里。否则用户会看到已经不可用的工作线程。
数据流:创建 worker → 调 close_agent 关闭它 → list_agents → 结果只剩 /root。
调用关系:它测试 ListAgentsHandlerV2 与 agent_control 关闭状态的配合。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
multi_agent_v2_list_agents_keeps_interrupted_resident_agents1753–1826 ↗
async fn multi_agent_v2_list_agents_keeps_interrupted_resident_agents()
作用:确认被打断但仍驻留的代理还会显示在列表里。打断不等于关闭。
数据流:创建 worker → 调 interrupt_agent → 再 list_agents → 结果仍包含 root 和 worker。
调用关系:它把 InterruptAgentHandler 和 ListAgentsHandlerV2 连起来,验证状态分类不要把 interrupted 当 closed。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
multi_agent_v2_send_message_rejects_legacy_items_field1829–1882 ↗
async fn multi_agent_v2_send_message_rejects_legacy_items_field()
作用:确认新版 send_message 不接受旧版 items 字段。这样消息协议更简单,也避免结构化输入绕过新版规则。
数据流:启用 MultiAgentV2 并创建 worker → send_message 传 items → 期望 unknown field items 错误。
调用关系:它测试 SendMessageHandlerV2 的严格参数解析。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert!, json!, panic!)。
multi_agent_v2_send_message_rejects_interrupt_parameter1885–1956 ↗
async fn multi_agent_v2_send_message_rejects_interrupt_parameter()
作用:确认新版 send_message 不支持顺带 interrupt 参数。发送消息和打断代理是两件不同的事。
数据流:创建 worker → send_message 传 message 和 interrupt=true → 参数解析失败 → 检查没有发出 Interrupt,也没有发消息。
调用关系:它保护 SendMessageHandlerV2 不被旧版 send_input 的 interrupt 语义污染。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert!, json!, panic!)。
multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn1959–2098 ↗
async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
作用:确认子代理每完成一轮 follow-up 工作,父代理都会收到一次完成通知。
数据流:创建 worker → 模拟第一次完成 → 发 followup_task → 模拟第二次完成 → 等待并检查父线程收到两条不同完成通知。
调用关系:它串起 SpawnAgentHandlerV2、FollowupTaskHandlerV2、会话事件和 format_inter_agent_completion_message,验证通知机制不会只触发一次。
调用图:调用 8 个内部函数(make_session_and_context, format_inter_agent_completion_message, function_payload, invocation, set_turn_config, thread_manager, root, try_from);外部调用 10 个(new, from_millis, from_secs, default, assert_eq!, json!, Completed, TurnComplete, sleep, timeout)。
multi_agent_v2_followup_task_rejects_legacy_items_field2101–2151 ↗
async fn multi_agent_v2_followup_task_rejects_legacy_items_field()
作用:确认新版 followup_task 不接受旧版 items 字段。后续任务也必须走新版 message 协议。
数据流:创建 worker → followup_task 传 items → 期望 unknown field items 错误。
调用关系:它测试 FollowupTaskHandlerV2 的参数边界,和 send_message 的同类测试一致。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert!, json!, panic!)。
multi_agent_v2_interrupted_turn_does_not_notify_parent2154–2228 ↗
async fn multi_agent_v2_interrupted_turn_does_not_notify_parent()
作用:确认子代理一轮被打断时,不会给父代理发送“完成”通知。打断不是完成。
数据流:创建 worker → 模拟该 worker 的轮次以 Interrupted 原因中止 → 查看根线程捕获的通信 → 应为空。
调用关系:它验证新版事件监听逻辑只对真正完成的轮次通知父代理。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, TurnAborted)。
multi_agent_v2_spawn_omits_agent_id_when_named2231–2267 ↗
async fn multi_agent_v2_spawn_omits_agent_id_when_named()
作用:确认新版命名创建代理后,输出里不暴露内部 agent_id,而返回 task_name 路径。
数据流:启用 MultiAgentV2 → 创建 test_process → 读取 JSON → 确认没有 agent_id,task_name 是 /root/test_process,nickname 为空。
调用关系:它验证 SpawnAgentHandlerV2 的新版返回格式,和旧版返回 agent_id 的测试相对应。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 6 个(new, default, assert!, assert_eq!, json!, from_str)。
multi_agent_v2_spawn_surfaces_task_name_validation_errors2270–2304 ↗
async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors()
作用:确认任务名不合法时,错误会直接清楚地返回给调用方。比如大写字母不允许。
数据流:启用 MultiAgentV2 → task_name 传 BadName → 调 spawn_agent → 返回只能用小写字母、数字、下划线的错误。
调用关系:它测试 SpawnAgentHandlerV2 对代理路径命名规则的校验。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
spawn_agent_reapplies_runtime_sandbox_after_role_config2307–2396 ↗
async fn spawn_agent_reapplies_runtime_sandbox_after_role_config()
作用:确认角色配置加载后,运行时沙箱和权限仍会重新套回子代理。沙箱就是限制文件、网络等访问的安全边界。
数据流:设置运行时审批、文件沙箱和权限配置 → 用 explorer 角色创建子代理 → 查询配置快照和新轮次策略 → 确认审批、沙箱、权限配置都和运行时一致。
调用关系:它测试 SpawnAgentHandler 在角色配置和运行时权限之间的优先级,防止角色文件覆盖掉安全设置。
调用图:调用 11 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, parse_agent_id, set_turn_config, thread_manager, from_runtime_permissions_with_enforcement, from_legacy_sandbox_policy, from_legacy_sandbox_policy_for_cwd (+1 more));外部调用 7 个(new, default, assert!, assert_eq!, assert_ne!, json!, from_str)。
spawn_agent_rejects_when_depth_limit_exceeded2399–2428 ↗
async fn spawn_agent_rejects_when_depth_limit_exceeded()
作用:确认旧版代理嵌套太深时不能再创建子代理。这样避免代理无限套娃。
数据流:把当前会话伪装成已经达到最大深度的子代理 → 调 spawn_agent → 返回深度限制错误。
调用关系:它验证 SpawnAgentHandler 使用 config.agent_max_depth 做保护。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, thread_manager);外部调用 6 个(new, default, SubAgent, assert_eq!, json!, panic!)。
spawn_agent_allows_depth_up_to_configured_max_depth2431–2474 ↗
async fn spawn_agent_allows_depth_up_to_configured_max_depth()
作用:确认如果配置把最大深度调高,旧版代理可以在新限制内继续创建子代理。
数据流:把 agent_max_depth 设置为默认值加一 → 当前深度设为默认最大值 → 调 spawn_agent → 成功返回 agent_id 和 nickname。
调用关系:它和深度超限测试配套,证明 SpawnAgentHandler 不是硬编码默认深度,而是读配置。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager);外部调用 7 个(new, default, SubAgent, assert!, assert_eq!, json!, from_str)。
multi_agent_v2_spawn_agent_ignores_configured_max_depth2477–2528 ↗
async fn multi_agent_v2_spawn_agent_ignores_configured_max_depth()
作用:确认新版多代理创建不受旧版最大深度配置限制。新版靠路径树和并发限制来管理,不用老的深度门槛。
数据流:启用 MultiAgentV2,把 agent_max_depth 设为 1,并让当前身份已经在深度 1 → spawn child → 仍成功返回 /root/parent/child。
调用关系:它验证 SpawnAgentHandlerV2 和旧版 SpawnAgentHandler 在深度规则上的差异。
调用图:调用 7 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, try_from);外部调用 6 个(new, default, SubAgent, assert_eq!, json!, from_str)。
send_input_rejects_empty_message2531–2546 ↗
async fn send_input_rejects_empty_message()
作用:确认旧版 send_input 不接受空消息。否则会给代理发送没有意义的输入。
数据流:构造 target 和空 message → 调 SendInputHandler → 返回空消息错误。
调用关系:它测试旧版按 agent_id 发送输入的基础校验。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 4 个(new, assert_eq!, json!, panic!)。
send_input_rejects_when_message_and_items_are_both_set2549–2570 ↗
async fn send_input_rejects_when_message_and_items_are_both_set()
作用:确认 send_input 不能同时传 message 和 items,避免同一次输入有两个来源。
数据流:构造同时包含 message 和结构化 items 的参数 → 调 SendInputHandler → 返回“二选一”错误。
调用关系:它验证 SendInputHandler 和 spawn_agent 使用一致的输入互斥规则。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 4 个(new, assert_eq!, json!, panic!)。
send_input_rejects_invalid_id2573–2588 ↗
async fn send_input_rejects_invalid_id()
作用:确认 send_input 遇到格式不对的代理编号会直接报错。
数据流:target 传 not-a-uuid → 调 SendInputHandler → 返回 invalid agent id 开头的错误。
调用关系:它测试 SendInputHandler 在查找代理前的编号解析。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 4 个(new, assert!, json!, panic!)。
send_input_reports_missing_agent2591–2609 ↗
async fn send_input_reports_missing_agent()
作用:确认编号格式正确但代理不存在时,会返回“找不到该代理”。
数据流:创建 ThreadManager 但不创建该随机 agent_id → 调 send_input → 返回 agent with id ... not found。
调用关系:它验证 SendInputHandler 和 ThreadManager 查询结果的错误映射。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, thread_manager, new);外部调用 4 个(new, assert_eq!, json!, panic!)。
send_input_interrupts_before_prompt2612–2651 ↗
async fn send_input_interrupts_before_prompt()
作用:确认 send_input 带 interrupt=true 时,会先打断代理,再发新输入。
数据流:启动一个代理线程 → 调 send_input 传 interrupt=true → 检查捕获操作顺序是 Interrupt 然后 UserInput。
调用关系:它测试 SendInputHandler 对操作顺序的保证,避免新输入被旧运行状态吞掉。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, thread_manager);外部调用 4 个(new, assert!, assert_eq!, json!)。
send_input_accepts_structured_items2654–2708 ↗
async fn send_input_accepts_structured_items()
作用:确认 send_input 可以发送结构化输入项,比如提到一个外部资源再附一段文字。
数据流:构造 Mention 和 Text 两个 items → 调 SendInputHandler → 检查线程收到的 Op::UserInput 精确包含这两个项。
调用关系:它验证旧版 SendInputHandler 对 items 协议的支持。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, vec!)。
resume_agent_rejects_invalid_id2711–2726 ↗
async fn resume_agent_rejects_invalid_id()
作用:确认恢复代理时,格式不对的编号会被拒绝。
数据流:id 传 not-a-uuid → 调 ResumeAgentHandler → 返回 invalid agent id 错误。
调用关系:它测试 ResumeAgentHandler 的输入解析防线。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 4 个(new, assert!, json!, panic!)。
resume_agent_reports_missing_agent2729–2747 ↗
async fn resume_agent_reports_missing_agent()
作用:确认恢复一个从未存在的代理时,会明确告诉调用方找不到。
数据流:准备随机 ThreadId → 调 resume_agent → 返回 agent with id ... not found。
调用关系:它验证 ResumeAgentHandler 在管理器里查不到线程时的错误。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, thread_manager, new);外部调用 4 个(new, assert_eq!, json!, panic!)。
resume_agent_noops_for_active_agent2750–2786 ↗
async fn resume_agent_noops_for_active_agent()
作用:确认恢复一个本来就活着的代理不会重复创建线程。它应该像“已经开着了,不用再开”。
数据流:启动一个线程并记录状态 → 调 resume_agent → 返回原状态 → 检查线程列表仍只有这个线程。
调用关系:它测试 ResumeAgentHandler 对活跃代理的幂等行为,也就是重复调用不会产生副作用。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager);外部调用 4 个(new, assert_eq!, json!, from_str)。
resume_agent_restores_closed_agent_and_accepts_send_input2789–2865 ↗
async fn resume_agent_restores_closed_agent_and_accepts_send_input()
作用:确认关闭后的代理可以从历史恢复,并且恢复后能继续接收输入。
数据流:用历史创建一个线程 → 关闭 live agent → 调 resume_agent → 状态不再 NotFound → 再 send_input → 返回有效 submission_id。
调用关系:它串起 ResumeAgentHandler 和 SendInputHandler,验证恢复后的代理不只是状态可见,还能真正工作。
调用图:调用 7 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager, from_auth_for_testing, from_api_key);外部调用 8 个(new, assert!, assert_eq!, assert_ne!, json!, Forked, from_str, vec!)。
resume_agent_rejects_when_depth_limit_exceeded2868–2897 ↗
async fn resume_agent_rejects_when_depth_limit_exceeded()
作用:确认旧版在深度超限时,不仅不能创建,也不能恢复代理。
数据流:把当前身份设到最大深度 → 调 resume_agent → 返回深度限制错误。
调用关系:它验证 ResumeAgentHandler 和 SpawnAgentHandler 使用同一套深度保护。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, thread_manager);外部调用 5 个(new, SubAgent, assert_eq!, json!, panic!)。
wait_agent_rejects_non_positive_timeout2900–2918 ↗
async fn wait_agent_rejects_non_positive_timeout()
作用:确认旧版 wait_agent 的 timeout_ms 必须大于 0。等待 0 毫秒没有实际意义。
数据流:传 timeout_ms=0 → 调 WaitAgentHandler → 返回 timeout_ms must be greater than zero。
调用关系:它测试旧版等待工具的参数校验。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
wait_agent_rejects_invalid_target2921–2936 ↗
async fn wait_agent_rejects_invalid_target()
作用:确认旧版 wait_agent 遇到无效代理编号会报错。
数据流:targets 里放 invalid → 调 WaitAgentHandler → 返回 invalid agent id 开头的错误。
调用关系:它验证 WaitAgentHandler 在订阅状态前先解析目标编号。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 5 个(new, default, assert!, json!, panic!)。
wait_agent_rejects_empty_targets2939–2954 ↗
async fn wait_agent_rejects_empty_targets()
作用:确认旧版 wait_agent 不能没有等待目标。否则不知道该等谁。
数据流:targets 传空数组 → 调 WaitAgentHandler → 返回 agent ids must be non-empty。
调用关系:它测试旧版 WaitAgentHandler 的基本输入要求。
调用图:调用 3 个内部函数(make_session_and_context, function_payload, invocation);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
multi_agent_v2_wait_agent_accepts_timeout_only_argument2957–3043 ↗
async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument()
作用:确认新版 wait_agent 可以只传超时时间,不必指定目标列表。新版主要等邮箱里有没有代理来信。
数据流:创建 worker → 启动 wait_agent 只传 timeout_ms → 往根代理邮箱塞一条 worker 消息 → wait 返回 Wait completed。
调用关系:它测试 WaitAgentHandlerV2 的新版等待模型:等邮箱通知,而不是等指定 agent_id 状态。
调用图:调用 8 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, root, new);外部调用 9 个(new, default, new, default, assert_eq!, json!, from_str, spawn, yield_now)。
multi_agent_v2_wait_agent_rejects_timeout_below_configured_min3046–3073 ↗
async fn multi_agent_v2_wait_agent_rejects_timeout_below_configured_min()
作用:确认新版 wait_agent 会拒绝小于配置最小值的超时。
数据流:把最小超时设为 50 → 请求 timeout_ms=1 → 调 WaitAgentHandlerV2 → 返回至少 50 的错误。
调用关系:它验证 WaitAgentHandlerV2 尊重 multi_agent_v2 的超时配置。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
multi_agent_v2_wait_agent_accepts_explicit_timeout_at_configured_min3076–3108 ↗
async fn multi_agent_v2_wait_agent_accepts_explicit_timeout_at_configured_min()
作用:确认 timeout_ms 正好等于配置最小值时是允许的。
数据流:配置最小值为 1 → 调 wait_agent timeout_ms=1 → 等待超时并返回 timed_out=true。
调用关系:它补充测试 WaitAgentHandlerV2 超时下限的边界值。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
multi_agent_v2_wait_agent_uses_configured_default_timeout3111–3163 ↗
async fn multi_agent_v2_wait_agent_uses_configured_default_timeout()
作用:确认新版 wait_agent 不传 timeout_ms 时,会使用配置里的默认超时。
数据流:默认超时设为 50 毫秒 → 先验证 20 毫秒内不会提前返回 → 再等待足够久 → 返回超时结果。
调用关系:它测试 WaitAgentHandlerV2 的默认参数来源。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config);外部调用 9 个(new, from_millis, from_secs, default, assert!, assert_eq!, json!, from_str, timeout)。
multi_agent_v2_wait_agent_allows_zero_configured_timeout3166–3203 ↗
async fn multi_agent_v2_wait_agent_allows_zero_configured_timeout()
作用:确认如果配置允许 0 超时,新版 wait_agent 可以立刻返回超时。
数据流:把最小、最大、默认超时都设为 0 → 调 wait_agent 不传参数 → 立即得到 timed_out=true。
调用关系:它测试 WaitAgentHandlerV2 对特殊配置的兼容性。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config);外部调用 7 个(new, from_secs, default, assert_eq!, json!, from_str, timeout)。
multi_agent_v2_wait_agent_rejects_timeout_above_configured_max3206–3233 ↗
async fn multi_agent_v2_wait_agent_rejects_timeout_above_configured_max()
作用:确认新版 wait_agent 会拒绝超过配置最大值的超时。
数据流:最大超时设为 50 → 请求 timeout_ms=500 → 返回 at most 50 错误。
调用关系:它验证 WaitAgentHandlerV2 的超时上限保护。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config);外部调用 5 个(new, default, assert_eq!, json!, panic!)。
multi_agent_v2_wait_agent_accepts_explicit_timeout_at_configured_max3236–3268 ↗
async fn multi_agent_v2_wait_agent_accepts_explicit_timeout_at_configured_max()
作用:确认 timeout_ms 正好等于配置最大值时是允许的。
数据流:配置最大值为 1 → 调 wait_agent timeout_ms=1 → 返回正常超时结果。
调用关系:它补充测试 WaitAgentHandlerV2 超时上限的边界值。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
wait_agent_returns_not_found_for_missing_agents3271–3304 ↗
async fn wait_agent_returns_not_found_for_missing_agents()
作用:确认旧版 wait_agent 等待不存在的代理时,会立刻报告 NotFound,而不是傻等到超时。
数据流:传两个随机 ThreadId → 调 wait_agent → 返回每个 id 对应 AgentStatus::NotFound,timed_out=false。
调用关系:它测试 WaitAgentHandler 对缺失目标的快速返回逻辑。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager, new);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
wait_agent_times_out_when_status_is_not_final3307–3347 ↗
async fn wait_agent_times_out_when_status_is_not_final()
作用:确认旧版 wait_agent 等待一个还没结束的代理时,会在超时后返回。
数据流:启动一个活跃线程 → wait_agent 等它,超时设为最小等待时间 → 返回空状态和 timed_out=true。
调用关系:它测试 WaitAgentHandler 对非最终状态的等待和超时处理。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager);外部调用 5 个(new, default, assert_eq!, json!, from_str)。
wait_agent_clamps_short_timeouts_to_minimum3350–3385 ↗
async fn wait_agent_clamps_short_timeouts_to_minimum()
作用:确认旧版 wait_agent 会把太短的超时提高到最小等待时间。这样避免调用方设置 10 毫秒导致忙等。
数据流:传 timeout_ms=10 等待活跃线程 → 用 50 毫秒外层超时检查它不会太早返回。
调用关系:它验证 WaitAgentHandler 的最小超时钳制行为。
调用图:调用 4 个内部函数(make_session_and_context, function_payload, invocation, thread_manager);外部调用 6 个(new, from_millis, default, assert!, json!, timeout)。
wait_agent_returns_final_status_without_timeout3388–3437 ↗
async fn wait_agent_returns_final_status_without_timeout()
作用:确认旧版 wait_agent 如果目标已经进入最终状态,会直接返回该状态。
数据流:启动线程后提交 Shutdown → 等状态变化到最终 → 调 wait_agent → 返回该线程 AgentStatus::Shutdown,timed_out=false。
调用关系:它测试 WaitAgentHandler 对已完成目标的快速路径。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager);外部调用 7 个(new, from_secs, default, assert_eq!, json!, from_str, timeout)。
multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity3440–3527 ↗
async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity()
作用:确认新版 wait_agent 收到邮箱消息时,只返回简短完成摘要。
数据流:创建 worker → 启动 wait_agent → 往根邮箱放一条 completed 消息 → 返回 message=Wait completed,timed_out=false。
调用关系:它验证 WaitAgentHandlerV2 等待邮箱活动的主要成功路径。
调用图:调用 8 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, root, new);外部调用 9 个(new, default, new, default, assert_eq!, json!, from_str, spawn, yield_now)。
multi_agent_v2_wait_agent_returns_for_already_queued_mail3530–3608 ↗
async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail()
作用:确认新版 wait_agent 如果调用前邮箱已经有消息,会立刻返回。
数据流:先创建 worker 并把消息放进根邮箱 → 再调用 wait_agent → 在很短时间内返回 Wait completed。
调用关系:它测试 WaitAgentHandlerV2 不会错过已经排队的邮件。
调用图:调用 8 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, root, new);外部调用 9 个(new, from_millis, default, new, default, assert_eq!, json!, from_str, timeout)。
multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification3611–3699 ↗
async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification()
作用:确认新版 wait_agent 会被任意子代理来信唤醒,不只盯着某一个。
数据流:创建 worker_a 和 worker_b → 启动 wait_agent → 让 worker_b 发来邮箱消息 → wait 返回完成。
调用关系:它验证 WaitAgentHandlerV2 的等待对象是当前代理邮箱整体。
调用图:调用 8 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, root, new);外部调用 9 个(new, default, new, default, assert_eq!, json!, from_str, spawn, yield_now)。
multi_agent_v2_wait_agent_does_not_return_completed_content3702–3788 ↗
async fn multi_agent_v2_wait_agent_does_not_return_completed_content()
作用:确认新版 wait_agent 不把子代理消息正文返回给模型。这样避免敏感输出通过等待结果泄露。
数据流:让 worker 往根邮箱发送 sensitive child output → wait_agent 返回 Wait completed → 检查输出字符串不包含敏感内容。
调用关系:它测试 WaitAgentHandlerV2 的隐私保护:只通知有消息,不转发消息内容。
调用图:调用 8 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager, root, new);外部调用 10 个(new, default, new, default, assert!, assert_eq!, json!, from_str, spawn, yield_now)。
multi_agent_v2_interrupt_agent_accepts_task_name_target3791–3895 ↗
async fn multi_agent_v2_interrupt_agent_accepts_task_name_target()
作用:确认新版 interrupt_agent 可以用任务名路径打断代理,而不是必须知道内部编号。
数据流:创建 /root/worker,再让 worker 创建 child → 对 target=worker 调 interrupt_agent → 返回之前状态,worker 收到 Interrupt,child 不被打断或关闭。
调用关系:它测试 InterruptAgentHandler 的路径解析和“只打断目标本身”的行为。
调用图:调用 6 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, set_turn_config, thread_manager);外部调用 7 个(new, default, assert!, assert_eq!, assert_ne!, json!, from_str)。
multi_agent_v2_interrupt_agent_accepts_unloaded_task_name_target3898–4005 ↗
async fn multi_agent_v2_interrupt_agent_accepts_unloaded_task_name_target()
作用:确认目标代理当前不在内存里时,新版 interrupt_agent 仍能接受任务名,并且不会把数据库里的边标记关闭。
数据流:启用 SQLite 状态库和新版多代理 → 创建 worker 后从内存移除并关掉旧线程 → interrupt target=worker → 返回 previous_status=NotFound → 检查数据库子边仍是 Open。
调用关系:它测试 InterruptAgentHandler、ThreadManager 和状态数据库在“卸载代理”场景下的协作。
调用图:调用 8 个内部函数(make_session_and_context, with_models_provider_home_and_state_for_tests, expect_text_output, function_payload, invocation, set_turn_config, default_for_tests, from_api_key);外部调用 6 个(new, default, assert_eq!, init_state_db, json!, from_str)。
multi_agent_v2_interrupt_agent_rejects_root_target_and_id4008–4055 ↗
async fn multi_agent_v2_interrupt_agent_rejects_root_target_and_id()
作用:确认不能用 interrupt_agent 打断根代理。根代理不是被 spawn 出来的子任务。
数据流:启用 MultiAgentV2 → target=/root 和 target=根线程 id 分别调用 interrupt_agent → 两次都返回 root is not a spawned agent。
调用关系:它验证 InterruptAgentHandler 对根代理路径和根代理编号都有保护。
调用图:调用 5 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager);外部调用 3 个(new, assert_eq!, json!)。
multi_agent_v2_interrupt_agent_rejects_self_target_by_id4058–4123 ↗
async fn multi_agent_v2_interrupt_agent_rejects_self_target_by_id()
作用:确认代理不能用自己的 id 打断自己。自我打断会让控制流很混乱。
数据流:把当前会话设成 worker 子代理 → interrupt_agent target=自己的 ThreadId → 返回“不能打断自己”的错误。
调用关系:它测试 InterruptAgentHandler 的自我目标检查。
调用图:调用 6 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager, try_from);外部调用 6 个(new, SubAgent, assert_eq!, default, json!, vec!)。
multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name4126–4191 ↗
async fn multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name()
作用:确认代理也不能用自己的任务路径打断自己。
数据流:当前身份是 /root/worker → interrupt_agent target=/root/worker → 返回同样的自我打断错误。
调用关系:它补充验证 InterruptAgentHandler 在路径目标上的自我检查。
调用图:调用 6 个内部函数(make_session_and_context, function_payload, invocation, set_turn_config, thread_manager, try_from);外部调用 6 个(new, SubAgent, assert_eq!, default, json!, vec!)。
close_agent_submits_shutdown_and_returns_previous_status4194–4230 ↗
async fn close_agent_submits_shutdown_and_returns_previous_status()
作用:确认关闭代理时会提交 Shutdown 操作,并把关闭前状态返回给调用方。
数据流:启动代理线程并记录状态 → 调 close_agent → 输出 previous_status → 检查线程收到 Op::Shutdown,之后状态变成 NotFound。
调用关系:它直接测试 CloseAgentHandler 的核心行为。
调用图:调用 5 个内部函数(make_session_and_context, expect_text_output, function_payload, invocation, thread_manager);外部调用 4 个(new, assert_eq!, json!, from_str)。
tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed4233–4438 ↗
async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed()
作用:检查关闭和恢复代理树时,子孙代理的状态能正确级联,同时显式关闭过的子树不会被父代理恢复误打开。
数据流:创建父、子、孙三层代理 → 关闭子代理,确认子孙都关 → 恢复子,确认子孙重开 → 再关子 → 关闭并恢复父 → 确认父重开但之前显式关闭的子孙仍关闭。
调用关系:它综合调用 SpawnAgentHandler、CloseAgentHandler、ResumeAgentHandler 和持久化线程存储,验证整棵代理树的生命周期规则。
调用图:调用 10 个内部函数(make_session_and_context, new, thread_store_from_config, expect_text_output, function_payload, invocation, parse_agent_id, default_for_tests, from_auth_for_testing, from_api_key);外部调用 9 个(new, from_secs, default, assert_eq!, assert_ne!, empty_extension_registry, init_state_db, json!, from_str)。
build_agent_spawn_config_uses_turn_context_values4441–4526 ↗
async fn build_agent_spawn_config_uses_turn_context_values()
作用:确认生成子代理启动配置时,会使用当前轮次的实际运行值,而不是只照抄静态配置。
数据流:在 TurnContext 中设置基础指令、开发者指令、沙箱、工作目录、审批策略和权限配置 → 调 build_agent_spawn_config → 构造期望 Config → 比较完全相等。
调用关系:它测试被 spawn_agent 使用的配置构造函数,确保子代理继承的是父轮次真实环境。
调用图:调用 6 个内部函数(make_session_and_context, default, from_runtime_permissions_with_enforcement, from_legacy_sandbox_policy, from_legacy_sandbox_policy_for_cwd, from);外部调用 3 个(from, assert_eq!, tempdir)。
build_agent_resume_config_clears_base_instructions4529–4564 ↗
async fn build_agent_resume_config_clears_base_instructions()
作用:确认恢复代理时会清掉 base_instructions。恢复旧线程应该用历史里的基础指令,不能把当前调用者的基础指令混进去。
数据流:给当前配置放 caller-base → 调 build_agent_resume_config → 期望输出配置 base_instructions=None,同时保留模型、审批、权限等运行时值。
调用关系:它测试 resume_agent 使用的配置构造函数,防止恢复旧代理时污染它原来的上下文。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 2 个(new, assert_eq!)。
core/src/tools/handlers/request_plugin_install_tests.rs源码 ↗
这个文件不是真正给用户运行的功能代码,而是“质检清单”。它用临时目录假装一个干净的 Codex 用户家目录,然后造出插件市场、插件配置、用户回应和配置文件,检查安装建议相关代码是否可靠。重点有三类:第一,只有插件真的安装完成后,系统才应该认为安装成功;远程插件建议则不走本地安装校验。第二,只有用户明确“拒绝,并且以后一直不要再提示”时,才会永久记住这个选择。第三,把这个选择写进 config.toml 时,要能区分连接器和插件,还要清理重复项、空白 ID 和多余空格。它像给安装提示流程做安全带,避免用户被反复打扰,也避免配置越写越乱。
verified_plugin_install_completed_requires_installed_plugin25–58 ↗
async fn verified_plugin_install_completed_requires_installed_plugin()
作用:这个测试确认:系统不能只因为某个插件出现在官方插件市场里,就说它已经安装好了。只有真正执行安装并刷新配置后,才应该判定安装完成。
数据流:进去的是一个临时的用户目录和一个假的官方插件市场,里面有名为 sample 的插件 → 测试先写入市场和功能配置,加载配置并创建 PluginsManager,然后检查未安装时结果必须是 false → 接着真的安装 sample 插件,再重新加载配置,最后检查结果变成 true。
调用关系:它模拟完整的本地插件安装场景,会用 curated_plugins_repo_path 找到官方插件仓库位置,用 write_openai_curated_marketplace、write_curated_plugin_sha、write_plugins_feature_config 准备测试数据,用 load_plugins_config 读取配置,并通过 PluginsManager::new 和 install_plugin 完成安装;核心是在不同阶段调用 verified_plugin_install_completed,确认判断逻辑没有提前放行。
调用图:调用 5 个内部函数(new, curated_plugins_repo_path, write_curated_plugin_sha, write_plugins_feature_config, try_from);外部调用 4 个(assert!, load_plugins_config, write_openai_curated_marketplace, tempdir)。
remote_plugin_install_suggestions_skip_core_installed_verification61–69 ↗
fn remote_plugin_install_suggestions_skip_core_installed_verification()
作用:这个测试确认远程插件建议会被单独识别出来。远程插件不需要像本地核心插件那样检查是否已经装到本机。
数据流:进去的是几个插件 ID 字符串 → 测试把带有 openai-curated-remote 后缀的 ID、普通 openai-curated 后缀的 ID、以及普通名字分别交给判断函数 → 出来的是 true 或 false,用来证明只有远程插件格式会被认出来。
调用关系:它直接围绕 is_remote_plugin_install_suggestion 做边界检查,确保后续安装提示流程遇到远程插件时,不会误走本地插件的安装完成校验。
调用图:外部调用 1 个(assert!)。
request_plugin_install_response_persists_only_decline_always_mode72–105 ↗
fn request_plugin_install_response_persists_only_decline_always_mode()
作用:这个测试确认系统只有在用户“拒绝安装,并选择以后一直不要提示”时,才会永久关闭这个安装建议。接受安装、只本次拒绝、或者没有附加信息,都不能被当成永久关闭。
数据流:进去的是几种假的用户回应 ElicitationResponse,ElicitationResponse 可以理解成“弹窗询问后的回答单” → 测试分别设置拒绝、接受、不同的保存选项和没有元数据的情况 → 出来的是布尔值,只有“Decline 加 always”这一种组合返回 true。
调用关系:它检查 request_plugin_install_response_requests_persistent_disable 这个判断函数,确保后面的配置写入动作只在用户真正表达“永远别再问”时发生,不会因为普通拒绝或接受而误写配置。
调用图:外部调用 1 个(assert!)。
persist_disabled_install_request_writes_connector_config108–126 ↗
async fn persist_disabled_install_request_writes_connector_config()
作用:这个测试确认当用户永久拒绝一个连接器安装建议时,系统会把这个连接器写进配置文件里的禁用列表。连接器可以理解成连接外部应用的入口,比如日历。
数据流:进去的是临时用户目录和一个用 connector_tool 造出来的 Google Calendar 连接器 → 测试调用 persist_disabled_install_request 写配置 → 然后读回 config.toml,把文本解析成 ConfigToml,最后确认 disabled_tools 里有 connector_calendar 这一项。
调用关系:它先借助 connector_tool 生成测试用连接器,再调用被测的 persist_disabled_install_request;之后通过 read_to_string 和 toml::from_str 检查磁盘上的真实文件内容,证明配置保存不是只停留在内存里。
调用图:调用 1 个内部函数(connector_tool);外部调用 4 个(assert_eq!, read_to_string, tempdir, from_str)。
persist_disabled_install_request_writes_plugin_config129–155 ↗
async fn persist_disabled_install_request_writes_plugin_config()
作用:这个测试确认当用户永久拒绝一个插件安装建议时,系统会把这个插件写进配置文件里的禁用列表。这里检查的是插件,不是连接器,避免两种工具被混在一起。
数据流:进去的是临时用户目录和一个手工构造的 Slack 插件信息 → 测试调用 persist_disabled_install_request 写配置 → 然后读回 config.toml 并解析,确认 disabled_tools 里出现的是 plugin 类型的 slack@openai-curated。
调用关系:它直接构造 DiscoverableTool::Plugin,交给 persist_disabled_install_request;再读取并解析配置文件,用 assert_eq 对比期望结构,确保插件分支的写入格式正确。
调用图:外部调用 7 个(new, new, assert_eq!, read_to_string, tempdir, from_str, Plugin)。
persist_disabled_install_request_dedupes_existing_disabled_tools158–208 ↗
async fn persist_disabled_install_request_dedupes_existing_disabled_tools()
作用:这个测试确认写入禁用工具时,系统会顺手整理已有配置:去掉重复项、修剪前后空格、忽略空 ID,同时保留原本有效的发现项和禁用项。
数据流:进去的是一个预先写好的 config.toml,里面故意放了重复的 connector_calendar、带空格的 ID、空白 ID,以及一个 Slack 插件 → 测试再次要求禁用同一个日历连接器 → 出来的配置应该只保留一个干净的 connector_calendar,并保留 slack@openai-curated 和原来的 discoverables。
调用关系:它用 std::fs::write 先制造一份“有点脏”的配置文件,用 connector_tool 创建目标连接器,再调用 persist_disabled_install_request;最后读回并解析配置,确认这个函数不只是追加内容,还会把禁用列表整理成稳定、干净的形态。
调用图:调用 1 个内部函数(connector_tool);外部调用 5 个(assert_eq!, read_to_string, write, tempdir, from_str)。
connector_tool210–226 ↗
fn connector_tool(id: &str, name: &str) -> DiscoverableTool
作用:这个小工具函数专门给测试造一个连接器对象,省得每个测试都手写一大串字段。它让测试更聚焦在“配置有没有写对”,而不是被造数据的细节淹没。
数据流:进去的是连接器 ID 和显示名称 → 函数把它们填进 AppInfo,其它不重要的字段设成 None、false、true 或空列表 → 出来的是 DiscoverableTool::Connector,也就是测试里可以直接拿去模拟安装建议的连接器。
调用关系:它被 persist_disabled_install_request_writes_connector_config 和 persist_disabled_install_request_dedupes_existing_disabled_tools 调用,作为测试数据工厂;它内部构造 AppInfo,再包成 Connector,交给后续的持久化测试使用。
调用图:被 2 处调用(persist_disabled_install_request_dedupes_existing_disabled_tools, persist_disabled_install_request_writes_connector_config);外部调用 3 个(new, new, Connector)。
core/src/tools/handlers/request_user_input_spec_tests.rs源码 ↗
这个文件不是正式运行时的功能,而是给 request_user_input 这个工具做“验收检查”。可以把它理解成一张出厂质检单:工具给模型看的 JSON Schema(也就是参数格式说明)必须包含问题、选项、自动超时等字段;autoResolutionMs(自动等待多久后继续)太小或太大时,要被夹到允许范围内;不同工作模式下能不能用这个工具,也要按功能开关变化。这里还检查工具描述文字是否准确告诉模型:什么时候能问用户,什么时候不能问。这样一来,用户界面、模型调用和模式限制三边就不容易对不上。
default_mode_enabled_available_modes12–16 ↗
fn default_mode_enabled_available_modes() -> Vec<ModeKind>
作用:这个小帮手用来得到“默认模式也允许使用 request_user_input”时的可用模式列表。测试里需要这个列表来比较功能开关打开前后的差别。
数据流:进去没有外部参数 → 它先拿一份默认功能配置,再打开 DefaultModeRequestUserInput 这个功能开关,最后把这份配置交给可用模式计算函数 → 出来的是一组 ModeKind,也就是哪些模式允许使用这个工具。
调用关系:它服务于后面的模式相关测试。测试需要模拟“默认模式开关已开启”的情况时,就调用它;它内部从 with_defaults 拿默认配置,再交给 request_user_input_available_modes 计算最终结果。
调用图:调用 1 个内部函数(with_defaults);外部调用 1 个(request_user_input_available_modes)。
default_available_modes18–20 ↗
fn default_available_modes() -> Vec<ModeKind>
作用:这个小帮手用来得到普通默认配置下,request_user_input 可以在哪些模式里用。它给测试提供一个稳定的基准。
数据流:进去没有外部参数 → 它创建默认功能配置,并直接拿这份配置去计算可用模式 → 出来的是默认情况下允许使用该工具的模式列表。
调用关系:它被后面的测试当作“没开额外功能开关”的对照组。它调用 with_defaults 准备默认配置,再调用 request_user_input_available_modes 得到模式列表。
调用图:调用 1 个内部函数(with_defaults);外部调用 1 个(request_user_input_available_modes)。
request_user_input_tool_includes_questions_schema23–114 ↗
fn request_user_input_tool_includes_questions_schema()
作用:这个测试确认 request_user_input 工具对外声明的参数格式是完整的,尤其是 questions 这个问题列表必须存在。没有这层检查,模型可能拿到错误的工具说明,导致传参不对。
数据流:进去是测试里手写的一份期望工具规格 → 它创建实际的 request_user_input 工具规格,并用断言把实际结果和期望结果逐项比较 → 出来没有业务返回值;如果不一致,测试失败,提醒开发者工具说明被改坏了。
调用关系:它是测试运行时由测试框架自动执行的检查点。它主要通过 assert_eq! 做比较,验证工具名、描述、严格模式、参数 schema,以及 questions、options、autoResolutionMs 等字段是否符合约定。
调用图:外部调用 1 个(assert_eq!)。
normalize_request_user_input_args_clamps_out_of_range_auto_resolution_ms117–156 ↗
fn normalize_request_user_input_args_clamps_out_of_range_auto_resolution_ms()
作用:这个测试确认 autoResolutionMs 超出允许范围时,会被自动拉回边界值。通俗说,就是用户或模型填了太短或太长的等待时间,系统会帮它改到安全范围内。
数据流:进去是一份包含问题和自动超时时间的参数,其中超时时间分别故意设得过小、过大 → 它调用参数规范化流程,并检查结果 → 出来应当是问题被补上“Other/其他”选项标记,超时时间被改成最小值或最大值;如果没有这样做,测试失败。
调用关系:它在测试阶段验证参数清洗规则。测试用 vec! 构造问题列表,再用 assert_eq! 确认规范化后的结果正好等于期望值,保证运行时代码不会接受离谱的自动等待时间。
调用图:外部调用 2 个(assert_eq!, vec!)。
normalize_request_user_input_args_accepts_auto_resolution_boundaries159–198 ↗
fn normalize_request_user_input_args_accepts_auto_resolution_boundaries()
作用:这个测试确认 autoResolutionMs 正好等于最小值或最大值时,会被接受而不是误改。它防止边界值被代码不小心当成非法值。
数据流:进去是一份合法参数,自动超时时间分别设置为允许范围的下限和上限 → 它执行参数规范化并比较结果 → 出来应保持对应的边界时间不变,同时问题仍会被补上“Other/其他”相关标记;不符合就测试失败。
调用关系:它和前一个越界测试配成一组:一个测越界要修正,一个测边界要保留。它用 vec! 准备问题数据,用 assert_eq! 把规范化结果和期望结果对齐。
调用图:外部调用 2 个(assert_eq!, vec!)。
request_user_input_tool_description_mentions_available_modes231–240 ↗
fn request_user_input_tool_description_mentions_available_modes()
作用:这个测试确认工具描述文字会清楚写出当前哪些模式可用。这样模型读到工具说明时,才知道自己什么时候可以调用它。
数据流:进去是两种可用模式列表:默认列表,以及开启 Default 模式后的列表 → 它生成工具描述文字,并和预期英文说明逐字比较 → 出来没有业务返回值;如果文字没有提到正确模式,测试失败。
调用关系:它是模式提示测试的文字版补充。前一个测试看不可用提示,这个测试看工具描述本身;它通过 assert_eq! 确保描述会随着可用模式变化,从“只在 Plan 模式”变成“Default 或 Plan 模式”。
调用图:外部调用 1 个(assert_eq!)。
core/src/tools/handlers/request_user_input_tests.rs源码 ↗
这个文件只做一件事:确认 request_user_input 这个工具不会被子代理线程使用。可以把主线程想成“前台接待员”,子代理线程像“后台帮手”。后台帮手可以做任务,但不能直接跑出来问用户问题;否则用户可能不知道是谁在问,也会让多代理流程变乱。测试先造出一个假的会话和一轮对话,然后故意把这一轮标记成 SubAgent,也就是子代理线程。接着它构造一次工具调用,内容里有一个选择题,模拟子代理想让用户在 A 和 B 之间选。最后测试调用 RequestUserInputHandler.handle,并检查结果必须失败,而且失败信息必须明确写着:request_user_input 只能由 root thread,也就是根线程使用。这样以后如果有人改代码不小心放开了限制,这个测试会立刻报错。
multi_agent_v2_request_user_input_rejects_subagent_threads15–68 ↗
async fn multi_agent_v2_request_user_input_rejects_subagent_threads()
作用:这个测试验证:当当前对话轮来自子代理线程时,调用 request_user_input 必须被拒绝。它保护的是用户交互边界,确保只有主线程能直接向用户提问。
数据流:进去的是测试自己搭出来的一套假会话、假对话轮,以及一份模拟的工具调用参数,里面包含一个要问用户的选择题。测试先把对话轮改成“子代理线程”来源,再把这些信息交给 RequestUserInputHandler.handle。出来的结果应该不是成功,而是一个错误;测试最后确认这个错误正好是“request_user_input can only be used by the root thread”,并且没有改成别的含糊错误。
调用关系:它在测试运行时由 Tokio 的异步测试框架启动。它先调用 make_session_and_context 准备测试用的会话环境,再用 ThreadId::new、ToolName::plain、CancellationToken::new、TurnDiffTracker::default 等工具拼出一次完整的工具调用,最后把实际判断交给 RequestUserInputHandler.handle。这个测试不负责实现拦截逻辑,只负责证明拦截逻辑在子代理场景下真的生效。
调用图:调用 4 个内部函数(make_session_and_context, default, new, plain);外部调用 8 个(new, new, new, SubAgent, assert_eq!, json!, panic!, new)。
core/src/tools/handlers/shell_spec_tests.rs源码 ↗
这里测试的不是命令真的跑不跑,而是这些命令工具的“菜单”和“表格”有没有写对。可以把工具规格想成餐厅菜单:名字、说明、必填项、可选项、返回格式都得清清楚楚,调用它的人才知道怎么点。这个文件会创建几个工具规格,比如 exec_command、write_stdin、request_permissions、shell_command,然后和手写的期望结果逐项比较。它还会根据系统是不是 Windows,检查说明文字是否包含 Windows 专门提示。另一个小测试会确认某些参数可以被隐藏,比如 shell 参数不暴露时,菜单里就真的没有它。因为这些规格通常会被外部接口或模型读取,所以哪怕只是描述文字、必填字段、权限参数少了一个,都可能让上层调用变得不可靠。
windows_shell_guidance_description5–7 ↗
fn windows_shell_guidance_description() -> String
作用:这个小函数把 Windows 下使用 shell 的额外提示包装成一段说明文字。它主要是为了让测试里拼接 Windows 专用描述时更清楚,不用到处重复同一段格式。
数据流:进去时不需要外部参数 → 它读取已有的 Windows shell 指引文字,并在前面加上两个换行,让它能自然接在主说明后面 → 出来的是一段格式整理好的字符串,不改动任何外部状态。
调用关系:它是测试里的辅助小零件。shell_command_tool_matches_expected_spec 在需要构造 Windows 版本的期望说明时会调用它;它自己只做字符串格式化,不再把工作交给项目里的其他测试函数。
调用图:被 1 处调用(shell_command_tool_matches_expected_spec);外部调用 1 个(format!)。
has_parameter9–14 ↗
fn has_parameter(tool: &ToolSpec, parameter_name: &str) -> bool
作用:这个函数用来检查某个工具规格里有没有指定名字的参数。它让测试可以用一句简单的话确认“这个参数有没有出现在对外说明书里”。
数据流:进去的是一个工具规格和一个参数名 → 它先把工具规格转成 JSON(一种常见的文本数据结构,方便按路径查字段),再去 /parameters/properties/参数名 这个位置找 → 出来的是 true 或 false,表示找到了还是没找到;如果工具规格连序列化都失败,测试会直接报错。
调用关系:它是参数存在性检查的小帮手。exec_command_tool_can_hide_shell_parameter 用它来确认 shell 参数被隐藏、cmd 参数仍然保留;它内部主要借助 JSON 转换和路径查询完成检查。
exec_command_tool_matches_expected_spec17–96 ↗
fn exec_command_tool_matches_expected_spec()
作用:这个测试确认 exec_command 工具的完整规格符合预期。exec_command 是更底层的执行命令工具,会声明命令、工作目录、shell、是否用伪终端等参数。
数据流:进去时由测试自己构造执行命令工具的选项,比如允许登录 shell、不启用执行权限审批 → 它生成实际工具规格,再手工拼出期望规格,包括工具名、说明文字、参数表、必填字段和输出格式;Windows 下会使用不同的说明文字 → 最后用断言比较两边是否完全相同,若有任何字段不一致,测试失败。
调用关系:它位于“规格防回归”流程里,专门盯住 exec_command 的公开契约。它会用到字符串、数字、布尔值这些 JSON Schema(给 JSON 参数用的格式说明)构造器,也会根据 cfg!(windows) 判断当前平台;最后交给 assert_eq! 做精确比对。
调用图:调用 3 个内部函数(boolean, number, string);外部调用 4 个(from, assert_eq!, cfg!, format!)。
exec_command_tool_can_hide_shell_parameter99–111 ↗
fn exec_command_tool_can_hide_shell_parameter()
作用:这个测试确认 exec_command 工具在需要时可以不暴露 shell 参数。这样上层可以限制调用者选择 shell,减少误用或安全风险。
数据流:进去时测试创建一个带选项的 exec_command 工具,并明确要求不要包含环境 ID、不要包含 shell 参数 → 它检查生成后的规格里 shell 参数不存在,同时 cmd 参数还存在 → 出来没有普通返回值;如果检查不成立,测试会失败。
调用关系:它补充验证 exec_command 的可配置外观。它主要依赖 has_parameter 这种检查思路来判断参数是否出现,并用 assert! 表达“必须没有 shell、必须有 cmd”的要求。
调用图:外部调用 1 个(assert!)。
write_stdin_tool_matches_expected_spec114–161 ↗
fn write_stdin_tool_matches_expected_spec()
作用:这个测试确认 write_stdin 工具的规格符合预期。write_stdin 用来给一个已经在运行的命令会话继续输入字符,并拿回最近输出。
数据流:进去时测试创建 write_stdin 工具 → 它手工列出期望参数,包括 session_id(会话编号)、chars(要写入的字符)、等待时间和输出字数预算,再写出期望的工具名、说明和输出格式 → 最后把实际规格和期望规格做完全比较,不一致就失败。
调用关系:它守住“继续和运行中命令交互”这个工具的公开接口。它使用 JSON Schema 的 number 和 string 来描述参数形状,用 assert_eq! 做最终核对。
调用图:调用 2 个内部函数(number, string);外部调用 2 个(from, assert_eq!)。
request_permissions_tool_includes_full_permission_schema164–200 ↗
fn request_permissions_tool_includes_full_permission_schema()
作用:这个测试确认 request_permissions 工具会带上完整的权限申请参数格式。这个工具让系统在一次操作中明确说明想要额外权限,而不是偷偷扩大权限。
数据流:进去时测试给工具一段描述文字,创建权限申请工具 → 它构造期望参数表:申请理由、环境 ID,以及真正的 permissions 权限配置结构,并指定 permissions 是必填项 → 最后比较实际工具规格和期望规格;缺字段、字段名错或必填项错都会让测试失败。
调用关系:它检查权限申请入口的“申请表”是否完整。它会用字符串 schema 和权限配置 schema 拼出预期表格,再通过 assert_eq! 和实际生成结果对齐,保证权限相关工具不会被无意简化。
调用图:调用 1 个内部函数(string);外部调用 2 个(from, assert_eq!)。
shell_command_tool_matches_expected_spec203–274 ↗
fn shell_command_tool_matches_expected_spec()
作用:这个测试确认 shell_command 工具的规格符合预期。shell_command 是面向普通 shell 命令的一层工具,说明文字和参数会因 Windows 与非 Windows 系统而不同。
数据流:进去时测试创建 shell_command 工具,并设置允许登录 shell、不启用执行权限审批 → 它根据当前平台拼出期望说明:Windows 下会写 PowerShell 示例并追加 Windows shell 指引,其他系统会强调要设置 workdir;然后构造 command、workdir、timeout_ms、login 等参数和权限参数 → 最后把实际工具规格与期望规格精确比较。
调用关系:它是 shell_command 对外契约的主测试。它在 Windows 分支会调用 windows_shell_guidance_description 来补上专门说明;同时使用 cfg!(windows) 选择平台文案,用 JSON Schema 构造参数说明,最后由 assert_eq! 判断整个工具规格是否被改动。
调用图:调用 4 个内部函数(windows_shell_guidance_description, boolean, number, string);外部调用 3 个(from, assert_eq!, cfg!)。
core/src/tools/handlers/shell_tests.rs源码 ↗
这个文件不是正式功能代码,而是一组自动测试。它盯住 ShellCommandHandler,也就是系统里把“我要执行一条命令”翻译成“操作系统真正能运行的参数”的部件。这里重点检查几件容易出错的事:不同 shell,比如 Bash、Zsh、PowerShell,生成出来的命令还能不能被安全命令识别器认出来;执行参数有没有正确带上当前会话的 shell、工作目录、环境变量、超时时间和沙箱权限;登录 shell(会先加载用户配置的 shell)在配置禁止时会不会被拒绝;以及命令执行前后给 hook(钩子,像门口的检查员,可在工具运行前后收到通知)的内容是不是原始命令和真实输出。可以把它理解成一套验收清单:只要底层命令包装方式一改,这些测试就会提醒开发者有没有把外部行为弄坏。
commands_generated_by_shell_command_handler_can_be_matched_by_is_known_safe_command30–58 ↗
fn commands_generated_by_shell_command_handler_can_be_matched_by_is_known_safe_command()
作用:这个测试确认 ShellCommandHandler 生成的命令格式,仍然能被“已知安全命令”检查器识别。这样像 ls 这类简单安全命令,不会因为被 shell 包了一层就被误判成危险。
数据流:它先准备 Bash 和 Zsh 的 shell 信息,再在机器上如果找得到 PowerShell 或 pwsh,也一起测试。每种 shell 都拿一条简单列目录命令进去,交给 assert_safe 检查;结果是这些被 shell 包装后的真实执行参数都应该被判断为安全。
调用关系:这是本文件第一组安全兼容性测试。它自己负责挑选要测试的 shell,PowerShell 路径通过 try_find_powershell_executable_blocking 和 try_find_pwsh_executable_blocking 查找,具体的安全断言交给 assert_safe 完成。
调用图:调用 3 个内部函数(assert_safe, try_find_powershell_executable_blocking, try_find_pwsh_executable_blocking);外部调用 1 个(from)。
assert_safe60–67 ↗
fn assert_safe(shell: &Shell, command: &str)
作用:这个辅助函数把同一条命令分别按“登录 shell”和“非登录 shell”两种方式包装,然后确认两种包装后的命令都能被安全检查器接受。
数据流:输入是一种 shell 和一条命令字符串。它调用 shell 自己的命令包装方法,分别生成两套执行参数;再把这些参数交给 is_known_safe_command 判断。它不返回数据,只要判断失败,测试就会直接报错。
调用关系:它是 commands_generated_by_shell_command_handler_can_be_matched_by_is_known_safe_command 的小工具,避免主测试重复写两遍登录和非登录 shell 的检查逻辑。
调用图:被 1 处调用(commands_generated_by_shell_command_handler_can_be_matched_by_is_known_safe_command);外部调用 1 个(assert!)。
shell_command_handler_to_exec_params_uses_session_shell_and_turn_context70–119 ↗
async fn shell_command_handler_to_exec_params_uses_session_shell_and_turn_context()
作用:这个测试确认 ShellCommandHandler 在生成真正执行参数时,会正确使用当前会话和当前轮次里的信息。也就是说,它不能凭空决定 shell、目录、环境变量、网络权限或超时时间。
数据流:它先创建一个测试用会话和轮次上下文,再准备一份工具调用参数,比如命令、工作目录、超时、沙箱权限和理由。随后它手动算出“应该得到什么”:用会话里的用户 shell 包装命令,用轮次上下文解析目录,用环境策略生成环境变量。最后调用 to_exec_params,逐项比较实际结果和预期结果。
调用关系:这是对 ShellCommandHandler::to_exec_params 的核心行为测试。测试数据来自 make_session_and_context,环境变量预期值由 create_env 构造,最后用断言确认转换过程没有漏掉上下文信息。
调用图:调用 3 个内部函数(create_env, make_session_and_context, to_exec_params);外部调用 1 个(assert_eq!)。
shell_command_handler_respects_explicit_login_flag122–147 ↗
fn shell_command_handler_respects_explicit_login_flag()
作用:这个测试确认如果调用方明确说要不要使用登录 shell,ShellCommandHandler 会尊重这个选择。登录 shell 可以理解为“先读用户 shell 配置再执行命令”的模式,非登录 shell 则更直接。
数据流:它准备一个 Bash shell,然后分别用 use_login_shell 为 true 和 false 调用 base_command。每次都拿结果和 shell 自己的 derive_exec_args 生成结果比较;如果两边一致,就说明处理器没有擅自改掉调用方的选择。
调用关系:它直接检查 ShellCommandHandler::base_command 这一层的命令包装行为。这里不需要完整会话,只关心显式登录标志是否被正确传下去。
调用图:调用 1 个内部函数(base_command);外部调用 2 个(from, assert_eq!)。
shell_command_handler_defaults_to_non_login_when_disallowed150–178 ↗
async fn shell_command_handler_defaults_to_non_login_when_disallowed()
作用:这个测试确认当配置不允许登录 shell,而且调用方也没有明确要求登录 shell 时,系统会安全地默认使用非登录 shell。这样既不会报错,也不会偷偷启用被禁止的模式。
数据流:它创建测试会话和轮次上下文,准备一条没有指定 login 字段的命令,然后调用 to_exec_params,并告诉它不允许登录 shell。输出的执行命令应该等于用当前用户 shell 按非登录方式包装出来的命令。
调用关系:它覆盖的是 ShellCommandHandler::to_exec_params 在配置受限时的默认选择。上下文仍由 make_session_and_context 提供,最终通过断言确认它走的是非登录 shell 路线。
调用图:调用 2 个内部函数(make_session_and_context, to_exec_params);外部调用 1 个(assert_eq!)。
shell_command_handler_rejects_login_when_disallowed181–191 ↗
fn shell_command_handler_rejects_login_when_disallowed()
作用:这个测试确认如果配置已经禁止登录 shell,而调用方还明确要求使用登录 shell,系统会拒绝。这样可以防止工具调用绕过管理员或配置文件设下的限制。
数据流:它把“明确要求登录 shell”和“不允许登录 shell”这两个条件传给 resolve_use_login_shell。预期输出不是成功结果,而是一个错误;测试再检查错误信息里是否说明登录 shell 被配置禁用了。
调用关系:它专门测试登录 shell 开关的判定函数 ShellCommandHandler::resolve_use_login_shell。前面的测试看默认行为,这里看冲突场景下是否会正确挡住。
调用图:调用 1 个内部函数(resolve_use_login_shell);外部调用 1 个(assert!)。
shell_command_pre_tool_use_payload_uses_raw_command194–217 ↗
async fn shell_command_pre_tool_use_payload_uses_raw_command()
作用:这个测试确认工具运行前发给 hook 的内容使用的是用户原始命令,而不是已经被 shell 包装过的复杂命令。这样外部检查器看到的是人真正想执行的内容,比较容易审查。
数据流:它构造一个工具调用载荷,里面只有原始命令 printf shell command。再创建测试会话、轮次和 ShellCommandHandler,调用 pre_tool_use_payload。期望结果是一个运行前通知,其中工具名是 bash,输入内容就是原始 command 字段。
调用关系:它检查 ShellCommandHandler 和 hook 系统之间的运行前接口。测试中的会话和轮次来自 make_session_and_context,处理器由 from 创建,最后用 JSON 断言确认传给 hook 的内容没有被改写。
调用图:调用 2 个内部函数(make_session_and_context, from);外部调用 2 个(assert_eq!, json!)。
build_post_tool_use_payload_uses_tool_output_wire_value220–250 ↗
async fn build_post_tool_use_payload_uses_tool_output_wire_value()
作用:这个测试确认工具运行后发给 hook 的响应内容,使用的是对外传输用的那份输出值。也就是说,hook 收到的不是内部临时结构,而是外部调用者会看到的结果。
数据流:它先准备一个带原始命令的工具调用,再准备一个工具输出,其中 post_tool_use_response 是 JSON 字符串 shell output。随后构造完整的工具调用对象并调用 post_tool_use_payload。结果应该包含工具名、调用编号、原始命令输入,以及刚才那份对外响应值。
调用关系:它检查 ShellCommandHandler 和 hook 系统之间的运行后接口。它用 make_session_and_context 建测试上下文,用 ToolName::plain 等构造调用对象,最后验证 post_tool_use_payload 是否把调用编号、输入和输出正确拼成后置 hook 消息。
调用图:调用 4 个内部函数(make_session_and_context, from, new, plain);外部调用 6 个(new, new, assert_eq!, json!, new, vec!)。
core/src/tools/handlers/unified_exec_tests.rs源码 ↗
这个文件像一张安全检查清单,检查系统在真正运行命令前后有没有做对事。这里的 shell 可以理解成“命令解释器”,比如 bash、PowerShell、cmd;系统要把用户写的 echo hello 包装成适合当前 shell 的真实启动命令。测试会确认:没指定 shell 时用默认 shell;指定 bash、PowerShell、cmd 时能识别;配置不允许登录 shell 时会拒绝;zsh-fork 模式下不允许用户再硬指定 shell。它还检查本地和远程环境的区别:远程执行时不能用本地专用的 zsh-fork 模式,要退回普通直连模式。后半部分测试“工具钩子”,也就是命令执行前后给外部扩展看的通知。它确认通知里用的是原始命令和真实输出,运行中的交互会话不会提前发完成通知,并且多个并行会话的结果不会串号。
invocation_for_payload24–40 ↗
async fn invocation_for_payload(
tool_name: &str,
call_id: &str,
payload: ToolPayload,
) -> ToolInvocation
作用:这是测试用的小帮手,用来快速做出一个假的工具调用现场。很多测试都需要“假装用户调用了 exec_command 或 write_stdin”,它就负责把这些通用材料拼好。
数据流:进去的是工具名、调用编号和一份工具输入内容 → 它创建一个测试用会话和回合,补上取消令牌、变更跟踪器、工具名、来源等固定字段 → 出来的是一个完整的 ToolInvocation,后面的测试可以直接拿它喂给处理器。
调用关系:它先调用 make_session_and_context 做出测试会话,再用 new、plain 等构造需要的辅助对象。后面多个 post_tool_use_payload 测试都会叫它来搭台,这样每个测试只关心自己要验证的输出行为,不用重复写一堆准备代码。
调用图:调用 3 个内部函数(make_session_and_context, new, plain);被 5 处调用(exec_command_post_tool_use_payload_skips_running_sessions, exec_command_post_tool_use_payload_uses_output_for_interactive_completion, exec_command_post_tool_use_payload_uses_output_for_noninteractive_one_shot_commands, write_stdin_post_tool_use_payload_keeps_parallel_session_metadata_separate, write_stdin_post_tool_use_payload_uses_original_exec_call_id_and_command_on_completion);外部调用 3 个(new, new, new)。
test_get_command_uses_default_shell_when_unspecified43–62 ↗
fn test_get_command_uses_default_shell_when_unspecified() -> anyhow::Result<()>
作用:这个测试确认:用户只给命令、不指定 shell 时,系统会自动使用默认 shell。这样用户不需要知道底层该用 bash、zsh 还是别的东西。
数据流:进去的是一段 JSON 参数,里面只有 cmd: echo hello → 测试先解析参数,确认 shell 字段为空,再把参数交给 get_command,并提供默认用户 shell → 出来的是包装好的命令数组,测试检查命令数量和最后真正执行的文本是不是 echo hello。
调用关系:它围绕 get_command 做验证,并通过 default_user_shell 提供默认值。这个测试保护的是最常见路径:调用者没指定 shell 时,命令执行仍然有一个合理的落点。
调用图:调用 1 个内部函数(default_user_shell);外部调用 3 个(new, assert!, assert_eq!)。
test_get_command_respects_explicit_bash_shell65–89 ↗
fn test_get_command_respects_explicit_bash_shell() -> anyhow::Result<()>
作用:这个测试确认:用户明确指定 /bin/bash 时,系统会尊重这个选择,而不是偷偷换成默认 shell。它还顺带检查在 PowerShell 风格包装时是否避免加载用户配置。
数据流:进去的是包含 cmd 和 shell: /bin/bash 的 JSON → 测试解析后确认 shell 字段确实是 /bin/bash,再交给 get_command 生成真实启动命令 → 出来的是命令参数列表,测试检查最后传入 shell 的仍是 echo hello,如果出现 PowerShell 的 -Command 参数,还必须带上 -NoProfile。
调用关系:它直接检验 get_command 处理“用户点名 shell”的分支。default_user_shell 仍会传进去,但这次只是备用;真正应该生效的是用户显式给出的 bash 路径。
调用图:调用 1 个内部函数(default_user_shell);外部调用 3 个(new, assert!, assert_eq!)。
test_get_command_respects_explicit_powershell_shell92–125 ↗
fn test_get_command_respects_explicit_powershell_shell() -> anyhow::Result<()>
作用:这个测试确认:当用户指定的 shell 看起来是 PowerShell 时,系统能认出来,并按 PowerShell 的方式包装命令。PowerShell 是 Windows 常见命令解释器,参数规则和 bash 不一样。
数据流:进去的是一个临时创建的 powershell 可执行文件路径,以及命令 echo hello → 测试把它写进 JSON,解析参数,再交给 get_command → 出来的是解析后的命令信息,测试检查命令文本位置正确,并且识别出的 shell 类型是 PowerShell。
调用关系:它使用 tempdir 和 write 临时造出一个像 PowerShell 的文件路径,避免依赖机器上真的安装了 PowerShell。核心仍是测试 get_command 的识别和包装逻辑。
调用图:调用 1 个内部函数(default_user_shell);外部调用 6 个(new, assert_eq!, cfg!, json!, write, tempdir)。
test_get_command_respects_explicit_cmd_shell128–146 ↗
fn test_get_command_respects_explicit_cmd_shell() -> anyhow::Result<()>
作用:这个测试确认:用户指定 Windows 的 cmd 时,系统会把它当成要使用的 shell。cmd 是 Windows 传统命令提示符,包装方式也和 Unix shell 不同。
数据流:进去的是包含 cmd: echo hello 和 shell: cmd 的 JSON → 测试解析参数,确认 shell 字段就是 cmd,再调用 get_command → 出来的是真实命令参数列表,测试检查其中放入的执行文本仍是 echo hello。
调用关系:它覆盖 get_command 对显式 cmd shell 的处理。default_user_shell 只是作为函数所需的默认值传入,测试重点是用户明确指定时不能被忽略。
调用图:调用 1 个内部函数(default_user_shell);外部调用 2 个(new, assert_eq!)。
test_get_command_rejects_explicit_login_when_disallowed149–166 ↗
fn test_get_command_rejects_explicit_login_when_disallowed() -> anyhow::Result<()>
作用:这个测试确认:如果配置不允许登录 shell,用户却要求 login: true,系统会拒绝。登录 shell 会读取更多用户启动配置,可能带来不可控行为,所以需要受配置约束。
数据流:进去的是带有 login: true 的 JSON 参数 → 测试解析参数后调用 get_command,但把 allow_login_shell 设成 false → 出来不是命令,而是错误信息;测试检查错误里明确说明登录 shell 被配置禁用。
调用关系:它验证 get_command 的安全边界。这个测试确保配置开关真的有效,而不是只写在配置里却没有拦住实际请求。
调用图:调用 1 个内部函数(default_user_shell);外部调用 2 个(new, assert!)。
test_get_command_rejects_explicit_shell_in_zsh_fork_mode169–199 ↗
fn test_get_command_rejects_explicit_shell_in_zsh_fork_mode() -> anyhow::Result<()>
作用:这个测试确认:在 zsh-fork 模式下,用户不能再指定自己的 shell。zsh-fork 可以理解成系统使用一套固定的 zsh 启动机制来执行命令,若再允许用户换 shell,就会破坏这套机制。
数据流:进去的是指定 /bin/bash 的 JSON,以及一个构造出来的 zsh-fork shell 模式配置 → 测试把 zsh 路径和执行包装器路径准备好,再调用 get_command → 出来的是错误信息;测试检查错误明确说本地 zsh-fork 执行不支持 shell 字段。
调用关系:它用 AbsolutePathBuf 和 ZshForkConfig 搭出 zsh-fork 环境,然后把请求交给 get_command。这个测试守住的是模式约束:一旦进入 zsh-fork,shell 选择权就不再交给单次调用参数。
调用图:调用 2 个内部函数(default_user_shell, from_absolute_path);外部调用 4 个(new, assert!, cfg!, ZshFork)。
shell_mode_for_environment_uses_direct_mode_for_remote_environments202–231 ↗
async fn shell_mode_for_environment_uses_direct_mode_for_remote_environments() -> anyhow::Result<()>
作用:这个测试确认:同样的 shell 模式配置,在本地环境和远程环境下会有不同选择;远程环境要使用 Direct,也就是普通直连执行模式。这样可以避免把本地专用的 zsh-fork 机制错误地套到远程服务器上。
数据流:进去的是一个 zsh-fork 模式、一个本地测试环境和一个带远程地址的测试环境 → 测试分别调用 shell_mode_for_environment → 出来是两个选择结果:本地保持 zsh-fork,远程变成 Direct。
调用关系:它通过 Environment::default_for_tests 做本地环境,通过 create_for_tests 做远程环境,然后验证 shell_mode_for_environment 的分流规则。这个测试连接了执行模式选择和运行地点判断。
调用图:调用 3 个内部函数(create_for_tests, default_for_tests, from_absolute_path);外部调用 3 个(assert_eq!, cfg!, ZshFork)。
exec_command_pre_tool_use_payload_uses_raw_command234–257 ↗
async fn exec_command_pre_tool_use_payload_uses_raw_command()
作用:这个测试确认:exec_command 在命令真正执行前发给钩子的内容,应该是用户原始写下的命令。钩子可以理解成“执行前后通知外部插件的消息”。
数据流:进去的是一个工具输入,里面有 cmd: printf exec command → 测试创建会话、构造 ToolInvocation,并调用 ExecCommandHandler 的 pre_tool_use_payload → 出来的是一个 PreToolUsePayload,里面的工具名是 bash,输入字段是 { command: 原始命令 }。
调用关系:它使用 make_session_and_context 搭测试现场,再让 ExecCommandHandler 生成执行前钩子消息。这个测试确保外部钩子看到的是用户写的命令,而不是系统后来包装过的复杂启动参数。
调用图:调用 2 个内部函数(make_session_and_context, default);外部调用 2 个(assert_eq!, json!)。
exec_command_pre_tool_use_payload_skips_write_stdin260–280 ↗
async fn exec_command_pre_tool_use_payload_skips_write_stdin()
作用:这个测试确认:write_stdin 这种“给已运行会话继续喂输入”的动作,不会触发执行前钩子。因为它不是新启动一条命令,只是往已有进程里写字符。
数据流:进去的是一个 write_stdin 的工具输入,里面有要写入的字符 → 测试构造 ToolInvocation,并调用 WriteStdinHandler 的 pre_tool_use_payload → 出来是 None,表示不发送执行前钩子消息。
调用关系:它和前一个 exec_command 测试形成对照:新命令会通知,写标准输入不会通知。这里的 WriteStdinHandler 明确跳过 pre_tool_use_payload,避免外部插件误以为又启动了一条新命令。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 2 个(assert_eq!, json!)。
exec_command_post_tool_use_payload_uses_output_for_noninteractive_one_shot_commands283–310 ↗
async fn exec_command_post_tool_use_payload_uses_output_for_noninteractive_one_shot_commands()
作用:这个测试确认:普通的一次性非交互命令执行完成后,系统会把命令和输出一起发给执行后钩子。非交互命令就是跑完就结束的命令,不需要用户继续输入。
数据流:进去的是 echo three 的工具输入,以及一份模拟执行结果,结果包含原始输出 three、退出码 0、没有进程仍在运行 → 测试用 invocation_for_payload 创建调用现场,再调用 ExecCommandHandler 的 post_tool_use_payload → 出来的是 PostToolUsePayload,里面包含调用编号、命令 echo three 和响应文本 three。
调用关系:它依赖 invocation_for_payload 准备 ToolInvocation,然后检查 ExecCommandHandler 在命令完成后如何生成钩子消息。这个测试覆盖最普通的“命令跑完,把结果告诉钩子”的路径。
调用图:调用 2 个内部函数(default, invocation_for_payload);外部调用 3 个(assert_eq!, json!, from_millis)。
exec_command_post_tool_use_payload_uses_output_for_interactive_completion313–341 ↗
async fn exec_command_post_tool_use_payload_uses_output_for_interactive_completion()
作用:这个测试确认:即使命令是交互式启动的,只要它已经完成,也会把最终输出发给执行后钩子。交互式可以理解成可能带终端、可能会继续等输入的运行方式。
数据流:进去的是带 tty: true 的 echo three 输入,以及一份表示进程已结束的输出,里面有退出码和文本 three → 测试构造调用现场,调用 ExecCommandHandler 的 post_tool_use_payload → 出来的是包含原命令和输出文本的 PostToolUsePayload。
调用关系:它和非交互测试一起说明:是否交互不是关键,关键是进程是否已经结束。只要完成了,ExecCommandHandler 就能把结果整理成给 bash 钩子的完成通知。
调用图:调用 2 个内部函数(default, invocation_for_payload);外部调用 3 个(assert_eq!, json!, from_millis)。
exec_command_post_tool_use_payload_skips_running_sessions344–363 ↗
async fn exec_command_post_tool_use_payload_skips_running_sessions()
作用:这个测试确认:如果一个命令还在运行,系统不会提前发送“执行后完成”钩子。否则外部插件会误以为命令已经结束,拿到的结果也可能只是半截。
数据流:进去的是 echo three 的工具输入,以及一份模拟输出,里面有 process_id 但没有 exit_code,表示进程还活着 → 测试构造调用现场,调用 ExecCommandHandler 的 post_tool_use_payload → 出来是 None,表示暂时不发完成通知。
调用关系:它用 invocation_for_payload 建立上下文,再验证 ExecCommandHandler 对运行中会话的判断。这个测试保护交互式或长时间运行命令的正确生命周期:没结束就不宣布结束。
调用图:调用 2 个内部函数(default, invocation_for_payload);外部调用 3 个(assert_eq!, json!, from_millis)。
write_stdin_post_tool_use_payload_uses_original_exec_call_id_and_command_on_completion366–398 ↗
async fn write_stdin_post_tool_use_payload_uses_original_exec_call_id_and_command_on_completion()
作用:这个测试确认:通过 write_stdin 让一个已有会话结束时,执行后钩子要归到最初启动命令的调用编号和命令上,而不是归到这次写输入的调用上。这样外部看日志时,结果会跟真正的命令对应起来。
数据流:进去的是 write_stdin 输入,以及一份模拟完成输出,输出里保存了原始 exec 调用编号 exec-call-45、原始命令 sleep 1; echo finished 和结果 finished\n → 测试构造 write_stdin 的调用现场,调用 WriteStdinHandler 的 post_tool_use_payload → 出来的是 PostToolUsePayload,tool_use_id 使用原始 exec 调用编号,命令和响应也来自完成的那个原命令。
调用关系:它验证 WriteStdinHandler 在会话完成时会把结果“还给”最初的 exec_command。这里 invocation_for_payload 只是创建 write_stdin 当前调用;真正发给钩子的身份信息来自 ExecCommandToolOutput。
调用图:调用 1 个内部函数(invocation_for_payload);外部调用 3 个(assert_eq!, json!, from_millis)。
write_stdin_post_tool_use_payload_keeps_parallel_session_metadata_separate401–455 ↗
async fn write_stdin_post_tool_use_payload_keeps_parallel_session_metadata_separate()
作用:这个测试确认:多个会话并行完成时,它们的命令、输出和调用编号不会互相串。就像两张外卖订单同时送达,系统必须把每份餐送给对应的人。
数据流:进去的是同一种 write_stdin 输入,以及两份不同的完成输出:A 是 alpha 和 exec-call-a,B 是 beta 和 exec-call-b → 测试分别构造两个调用现场,按 B 再 A 的顺序调用 WriteStdinHandler 的 post_tool_use_payload → 出来的是两个 PostToolUsePayload,各自保留自己的调用编号、命令和输出。
调用关系:它两次使用 invocation_for_payload 创建并行场景,然后检查 WriteStdinHandler 不会依赖某个全局临时状态把结果弄混。这个测试特别保护并发情况下的元数据隔离。
调用图:调用 1 个内部函数(invocation_for_payload);外部调用 3 个(assert_eq!, json!, from_millis)。
core/src/tools/hosted_spec_tests.rs源码 ↗
这个文件不负责真正生成图片或搜索网页,它像一张验收清单,专门检查别的代码拼出来的工具说明是否符合预期。这里的“工具规格”可以理解成给模型或外部接口看的菜单:告诉对方有哪些工具、格式是什么、能不能联网、搜索范围和用户位置怎么填。测试会直接调用创建工具规格的函数,然后用断言(assert,一种“结果必须等于我预期”的检查)比较实际结果和标准答案。它特别关心三件事:图片生成工具会不会保留指定的输出格式;网页搜索工具会不会把域名限制、用户大致位置、搜索内容类型等配置原样带过去;当网页搜索被关闭时,系统会不会真的不提供这个工具。没有这些测试,后续修改工具配置代码时,可能表面能编译,实际却把联网开关、搜索范围或图片格式传错。
image_generation_tool_matches_expected_spec11–18 ↗
fn image_generation_tool_matches_expected_spec()
作用:这个测试确认图片生成工具的规格会正确记录输出格式。比如传入“png”,生成出来的工具说明里也必须写着“png”。
数据流:进去的是一个固定的格式字符串“png” → 测试调用创建图片生成工具规格的函数,拿到实际生成结果 → 它把这个结果和手写的标准答案比较;如果不一样,测试失败,不改外部状态。
调用关系:它在测试运行时由测试框架自动调用。它把主要工作交给被测的图片工具创建函数,然后用 assert_eq! 这个断言宏做最终验收。
调用图:外部调用 1 个(assert_eq!)。
web_search_tool_preserves_configured_options21–56 ↗
fn web_search_tool_preserves_configured_options()
作用:这个测试确认网页搜索工具不会丢掉用户配置。它检查允许访问的域名、用户大致位置、搜索上下文大小,以及“文字加图片”搜索类型是否都被正确放进最终工具规格里。
数据流:进去的是一整套网页搜索选项:开启实时搜索、限制域名为 example.com、用户位置大致在美国并带时区、搜索上下文大小为低、内容类型为文字和图片 → 测试调用网页搜索工具创建函数 → 出来应该是一个带完整配置的网页搜索工具规格;测试把实际结果和预期结果逐项比较,不写入文件或网络。
调用关系:它由测试框架在测试阶段自动运行。它扮演的是“复杂配置验收员”:把一份较完整的配置交给被测函数,再用 assert_eq! 确认这些配置在转换成对外工具规格时没有被遗漏或改错。
调用图:外部调用 1 个(assert_eq!)。
web_search_tool_is_absent_when_disabled59–68 ↗
fn web_search_tool_is_absent_when_disabled()
作用:这个测试确认当网页搜索被明确关闭时,系统不会偷偷提供网页搜索工具。它是在检查一个安全又直观的开关行为:关了就应该没有。
数据流:进去的是网页搜索选项,其中模式被设为 Disabled,意思是关闭;没有额外搜索配置,工具类型设为文字搜索 → 测试调用网页搜索工具创建函数 → 出来应该是 None,也就是“不创建这个工具”;然后用断言确认结果确实为空。
调用关系:它同样由测试框架自动调用。它关注的是关闭分支:被测函数如果发现搜索模式是关闭,就应直接返回没有工具,测试再用 assert_eq! 验证这个决定。
调用图:外部调用 1 个(assert_eq!)。
路由、注册表和工具暴露
此组从工具结果塑形延伸到注册表分发、路由器行为,以及决定暴露哪些工具的规划逻辑。
core/src/tools/context_tests.rs源码 ↗
这里测试的是工具执行完以后,系统怎么把结果交还给对话模型。可以把它想成“快递打包检查”:不同工具寄来的东西不一样,有普通函数结果、自定义工具结果、MCP 工具结果、搜索工具结果、命令行执行结果;这个文件逐个检查它们有没有被装进正确的盒子。测试会确认调用编号没有丢,成功状态没有丢,文字、图片、结构化 JSON 数据都按规则保留。它也特别检查两件容易出问题的事:一是输出太长时要截断,避免日志或上下文被撑爆;二是 MCP 和命令行结果要带上耗时、退出码、原始 token 数等说明,让人能看懂工具到底运行得怎么样。这里没有真正连网络或跑外部工具,主要是在内存里造出假结果,再检查转换后的形状是否符合预期。
custom_tool_calls_should_roundtrip_as_custom_outputs9–27 ↗
fn custom_tool_calls_should_roundtrip_as_custom_outputs()
作用:这个测试确认“自定义工具”的输出,最后仍然会被包装成自定义工具输出,而不会被误当成普通函数输出。这样调用方才能按原来的工具类型正确读取结果。
数据流:进去的是一个自定义工具载荷和一段文字结果“patched” → 测试用 from_text 造出工具输出,再转成响应项 → 出来应是 CustomToolCallOutput,里面的调用编号、正文文字和成功状态都要和输入一致;如果类型不对就直接失败。
调用关系:它专门覆盖 FunctionToolOutput 转响应项这条路里的“自定义工具”分支。测试先借助 from_text 做出标准输出,再检查转换结果,防止以后有人改代码时把自定义工具误归到函数工具里。
调用图:调用 1 个内部函数(from_text);外部调用 2 个(assert_eq!, panic!)。
function_payloads_remain_function_outputs30–46 ↗
fn function_payloads_remain_function_outputs()
作用:这个测试确认普通函数工具的输出,会继续以普通函数调用输出的形式返回。它防止系统把函数工具和自定义工具的返回格式混在一起。
数据流:进去的是一个函数工具载荷和文字结果“ok” → 测试把它转成响应项 → 出来应是 FunctionCallOutput,调用编号是“fn-1”,正文是“ok”,成功状态是 true。
调用关系:它和自定义工具的测试形成一对,分别守住两种工具载荷的分流规则。它调用 from_text 构造结果,然后检查最终包装类型是否正确。
调用图:调用 1 个内部函数(from_text);外部调用 2 个(assert_eq!, panic!)。
mcp_code_mode_result_serializes_full_call_tool_result49–86 ↗
fn mcp_code_mode_result_serializes_full_call_tool_result()
作用:这个测试确认 MCP 工具在“代码模式”下会保留完整的原始结果。MCP 可以理解为一种让模型调用外部工具的协议;代码模式需要的是原样 JSON,而不是给人看的精简文本。
数据流:进去的是一个 CallToolResult,里面有普通内容、结构化内容、错误标记和元数据 → 调用 code_mode_result 生成代码模式结果 → 出来的 JSON 必须完整包含 content、structuredContent、isError 和 _meta。
调用关系:它检查 MCP 结果给代码模式使用时的通道。这里不经过普通展示用的截断和格式化,而是确保下游代码能拿到完整机器可读数据。
调用图:外部调用 3 个(assert_eq!, json!, vec!)。
mcp_tool_output_response_item_includes_wall_time89–136 ↗
fn mcp_tool_output_response_item_includes_wall_time()
作用:这个测试确认 MCP 工具的普通响应里会写上运行耗时。这样用户或模型看到结果时,不只知道输出是什么,也知道工具跑了多久。
数据流:进去的是一个耗时 1.25 秒、内容为“done”的 MCP 输出 → 转成函数调用响应项 → 出来的正文应以“Wall time: 1.2500 seconds”开头,后面跟着可解析的 JSON 内容,并且成功状态为 true。
调用关系:它测试 MCP 输出走普通 response item 的格式化路径。过程中会把结果序列化成文本,再用 JSON 解析检查文本里的输出确实没有变形。
调用图:外部调用 7 个(assert_eq!, json!, panic!, Bytes, from_str, from_millis, vec!)。
mcp_tool_output_response_item_truncates_large_structured_content139–179 ↗
fn mcp_tool_output_response_item_truncates_large_structured_content()
作用:这个测试确认 MCP 工具返回很大的结构化内容时,普通响应会被截断。这样可以避免一次工具结果把对话上下文或日志撑得太大。
数据流:进去的是一个带超大 structured_content 的 MCP 输出,并设置最多 128 字节的截断策略 → 转成响应项 → 出来的文本应包含耗时和“已截断”的提示,而且不应再使用被结构化内容覆盖的普通 content 文本。
调用关系:它覆盖 MCP 普通展示路径里的“大输出保护”。当 structured_content 存在时,系统优先展示它;如果太大,就交给截断策略处理。
调用图:外部调用 8 个(assert!, assert_eq!, json!, panic!, Bytes, json!, from_millis, vec!)。
mcp_tool_output_response_item_preserves_content_items182–232 ↗
fn mcp_tool_output_response_item_preserves_content_items()
作用:这个测试确认 MCP 输出里如果有图片,转换后仍然保留成图片内容项,而不是只剩下一段文字。这样模型还能继续看到图片这类非文字信息。
数据流:进去的是一个包含图片数据的 MCP 输出和 0.5 秒耗时 → 转成函数调用响应项 → 出来的 content_items 应包含一段耗时说明文字和一个图片项;纯文本正文则只保留耗时说明。
调用关系:它检查 MCP 输出的多模态内容路径。多模态是指文字、图片等多种内容形式一起传递;这个测试防止格式化时把图片丢掉。
调用图:外部调用 6 个(assert_eq!, json!, panic!, Bytes, from_millis, vec!)。
mcp_tool_output_code_mode_result_stays_raw_call_tool_result235–272 ↗
fn mcp_tool_output_code_mode_result_stays_raw_call_tool_result()
作用:这个测试确认 MCP 工具在代码模式下不会因为普通显示的截断策略而丢数据。即使内容很大,代码模式也应拿到原始完整结果。
数据流:进去的是一个超大的结构化内容和很小的截断限制 → 调用 code_mode_result → 出来的 JSON 仍然包含完整 structuredContent,没有被截短,也不带普通展示用的耗时头。
调用关系:它和普通响应截断测试相互补充:普通给人看的结果要保护长度,代码模式给程序用的结果要保真。这个测试守住两条路径的区别。
调用图:外部调用 6 个(assert_eq!, json!, Bytes, json!, from_millis, vec!)。
custom_tool_calls_can_derive_text_from_content_items275–319 ↗
fn custom_tool_calls_can_derive_text_from_content_items()
作用:这个测试确认自定义工具如果返回的是一组内容项,系统还能从里面提取出可读文字。图片会被保留为内容项,但不会硬塞进纯文本正文。
数据流:进去的是两个文字项和一个图片项组成的输出 → from_content 生成工具输出,再转成自定义工具响应项 → 出来的内容项完整保留,纯文本正文则变成“line 1\nline 2”,成功状态也保留。
调用关系:它测试 FunctionToolOutput 的内容项路径,尤其是从混合内容里抽取文字的规则。它保证自定义工具既能保留图片,又能给只看文本的地方提供摘要。
调用图:调用 1 个内部函数(from_content);外部调用 3 个(assert_eq!, panic!, vec!)。
tool_search_payloads_roundtrip_as_tool_search_outputs322–372 ↗
fn tool_search_payloads_roundtrip_as_tool_search_outputs()
作用:这个测试确认“工具搜索”的结果会被包装成工具搜索输出。工具搜索就是让系统按查询词返回可用工具列表,比如搜索“calendar”后返回创建日程的工具。
数据流:进去的是一个搜索请求和一个可加载的函数工具说明 → 转成响应项 → 出来应是 ToolSearchOutput,带原调用编号、completed 状态、client 执行来源,以及序列化后的工具定义 JSON。
调用关系:它覆盖工具搜索这条独立返回通道。普通函数输出、自定义工具输出和搜索输出格式不同,这个测试防止工具列表被误包装成普通函数结果。
调用图:外部调用 3 个(assert_eq!, panic!, vec!)。
log_preview_uses_content_items_when_plain_text_is_missing375–388 ↗
fn log_preview_uses_content_items_when_plain_text_is_missing()
作用:这个测试确认日志预览在没有现成纯文本时,会从内容项里提取文字。这样日志里仍然能看到简短、有用的结果摘要。
数据流:进去的是一个只含 InputText 内容项、没有直接纯文本正文的工具输出 → 调用 log_preview 和内容项转文本函数 → 出来都应得到“preview”。
调用关系:它测试日志预览的兜底路径。FunctionToolOutput 可能保存的是内容项而不是一整段文本,这个测试确保日志系统仍能显示可读内容。
调用图:调用 1 个内部函数(from_content);外部调用 2 个(assert_eq!, vec!)。
telemetry_preview_returns_original_within_limits391–394 ↗
fn telemetry_preview_returns_original_within_limits()
作用:这个测试确认遥测预览在内容很短时不会乱改原文。遥测可以理解为系统运行时收集的简短诊断信息。
数据流:进去的是短字符串“short output” → 调用 telemetry_preview → 出来应原样返回,不添加截断提示,也不删内容。
调用关系:它守住 telemetry_preview 的正常情况:只有超出限制才动刀,没超出就保持原样。
调用图:外部调用 1 个(assert_eq!)。
telemetry_preview_truncates_by_bytes397–406 ↗
fn telemetry_preview_truncates_by_bytes()
作用:这个测试确认遥测预览会按字节大小截断过长内容。字节可以粗略理解为计算机存文字占用的空间单位。
数据流:进去的是比最大字节限制更长的一串 x → 调用 telemetry_preview → 出来的预览应包含截断提示,并且总长度不会超过允许范围太多。
调用关系:它测试 telemetry_preview 的“太大就剪短”规则,避免遥测记录因为单条输出太长而占用过多空间。
调用图:外部调用 1 个(assert!)。
telemetry_preview_truncates_by_lines409–420 ↗
fn telemetry_preview_truncates_by_lines()
作用:这个测试确认遥测预览也会按行数截断。即使每行不长,行数太多也会让日志难读,所以需要限制。
数据流:进去的是超过最大行数的多行文本 → 调用 telemetry_preview → 出来的文本行数应在限制内,最后一行应是截断提示。
调用关系:它和按字节截断测试一起覆盖 telemetry_preview 的两种保护栏:内容不能太占空间,也不能太多行。
调用图:外部调用 2 个(assert!, assert_eq!)。
exec_command_tool_output_formats_truncated_response423–463 ↗
fn exec_command_tool_output_formats_truncated_response()
作用:这个测试确认命令行工具输出被截断时,返回文本仍然包含关键信息。命令行工具就是运行一个系统命令;即使输出太长,也要保留耗时、退出码、原始 token 数等线索。
数据流:进去的是一次模拟命令执行结果:调用编号、分块编号、耗时、原始输出、token 截断限制、退出码和原始 token 数 → 转成函数调用响应项 → 出来的文本应包含 Chunk ID、Wall time、退出码、Original token count、Output,以及“tokens truncated”提示;成功状态应为 true。
调用关系:它测试 ExecCommandToolOutput 的响应格式化路径,特别是和 TruncationPolicy::Tokens 相关的截断展示。最后用正则匹配检查整体文本结构,确保给模型和日志看的命令结果既简短又有上下文。
调用图:外部调用 5 个(assert_eq!, assert_regex_match, panic!, Tokens, from_millis)。
core/src/tools/registry_tests.rs源码 ↗
可以把工具注册表想成一个前台接线员:模型说要调用某个工具,它就要按名字找到正确的人去办事,还要在办事前后通知旁边的记录员或扩展系统。这个测试文件专门搭了一批假的工具处理器,模拟成功、失败、带命名空间的名字、特殊工具、钩子改写输入等情况。它检查几件容易出错的事:同名但不同命名空间的工具不能串门;普通函数工具会把输入和输出整理成钩子能看懂的格式;某些内部工具不会暴露默认钩子数据;工具执行后的反馈既能给模型看文本,也能保留原来的结构化结果;执行工具时必须发出“开始”和“结束”生命周期通知。没有这些测试,工具系统改动后可能出现找错工具、钩子拿到错误数据、扩展收不到通知这类隐蔽问题。
TestHandler::tool_name9–11 ↗
fn tool_name(&self) -> codex_tools::ToolName
作用:返回这个假工具处理器自己的工具名字。测试里用它来模拟真实工具告诉系统“我叫什么”。
数据流:进去的是这个 TestHandler 自身,里面存着一个 tool_name → 函数把这个名字复制一份 → 出来的是可交给注册表或调用流程使用的工具名,不改动原对象。
调用关系:这是 TestHandler 实现工具执行接口的一部分。测试代码把 TestHandler 当成真实工具用时,注册表或工具调用流程会通过这个入口拿到它的名字。
调用图:外部调用 1 个(clone)。
TestHandler::spec13–15 ↗
fn spec(&self) -> codex_tools::ToolSpec
作用:生成这个假工具的说明书。说明书告诉外部系统:这个工具叫什么、是函数工具、参数长什么样。
数据流:进去的是 TestHandler 自身,读取其中的 tool_name → 交给 test_spec 统一制作一个测试用工具规格 → 出来的是 ToolSpec,不改动处理器。
调用关系:它把具体制作规格的活交给 test_spec。这样多个测试处理器都能用同一套假说明书,避免每个测试重复写。
调用图:调用 1 个内部函数(test_spec)。
TestHandler::handle17–26 ↗
fn handle(&self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:模拟工具被调用后总是成功返回“ok”。它让测试不用依赖真实工具,也能跑完整个工具调用流程。
数据流:进去的是一次 ToolInvocation,但这个假实现不读取里面的具体内容 → 创建一个异步任务,里面包装一段文本输出“ok”,并标记成功 → 出来的是一个未来会完成的工具输出结果,不修改输入。
调用关系:这是 TestHandler 扮演真实工具时的执行入口。注册表调度工具时会调用它;它内部用 from_text 做出模型可见的文本结果。
LifecycleTestHandler::tool_name43–45 ↗
fn tool_name(&self) -> codex_tools::ToolName
作用:返回生命周期测试用假工具的名字。它让测试能区分“成功工具”和“失败工具”。
数据流:进去的是 LifecycleTestHandler 自身 → 复制里面保存的 tool_name → 出来的是工具名,处理器状态保持不变。
调用关系:这是生命周期测试处理器实现工具接口的一部分。dispatch_notifies_tool_lifecycle_contributors 里注册两个这种处理器,靠这个名字识别它们。
调用图:外部调用 1 个(clone)。
LifecycleTestHandler::spec47–49 ↗
fn spec(&self) -> codex_tools::ToolSpec
作用:为生命周期测试用工具生成一份普通的测试工具规格。它不关心成功或失败,只说明工具是什么。
数据流:进去的是处理器自身,读取 tool_name → 调用 test_spec 生成通用测试规格 → 出来的是 ToolSpec。
调用关系:和 TestHandler::spec 一样,它复用 test_spec,保证测试中的假工具都有一致的工具说明。
调用图:调用 1 个内部函数(test_spec)。
LifecycleTestHandler::handle51–53 ↗
fn handle(&self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:把真正的模拟执行交给 handle_call,并包装成异步返回。它是生命周期测试工具被调用时的统一入口。
数据流:进去的是一次工具调用,但它不直接处理内容 → 调用 handle_call 生成实际成功或失败结果,再把它放进异步任务 → 出来的是工具执行的异步结果。
调用关系:注册表调度这个假工具时会先到这里。这里再转给 LifecycleTestHandler::handle_call,让测试可以专门控制工具是成功还是报错。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
LifecycleTestHandler::handle_call57–72 ↗
async fn handle_call(
&self,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError>
作用:按测试预设模拟工具执行成功或失败。它用来检查工具结束通知里记录的结果是否准确。
数据流:进去的是处理器自身,读取 result 这个预设结果 → 如果是 Ok,就返回文本“ok”,并带上 success 标记;如果是 Err,就返回一个给模型看的错误“handler failed” → 出来的是成功的工具输出,或者一个失败错误。
调用关系:它只由 LifecycleTestHandler::handle 调用。dispatch_notifies_tool_lifecycle_contributors 依靠它制造一次成功调用和一次失败调用,再检查生命周期记录是否符合预期。
调用图:调用 1 个内部函数(from_text);被 1 处调用(handle);外部调用 3 个(new, clone, RespondToModel)。
test_spec77–86 ↗
fn test_spec(tool_name: &codex_tools::ToolName) -> codex_tools::ToolSpec
作用:快速做出一份测试用工具说明书。它避免每个假工具都手写一遍重复的规格信息。
数据流:进去的是一个工具名 → 用这个名字创建一个函数类工具规格,描述写成“Test tool.”,参数 schema 使用默认空结构 → 出来的是 ToolSpec::Function。
调用关系:TestHandler::spec 和 LifecycleTestHandler::spec 都调用它。它是这些测试假工具共同的“说明书工厂”。
ToolLifecycleRecorder::on_tool_start106–121 ↗
fn on_tool_start(
&'a self,
input: codex_extension_api::ToolStartInput<'a>,
) -> codex_extension_api::ToolLifecycleFuture<'a>
作用:记录某个工具刚开始执行这件事。它像测试里的记账本,用来确认系统确实发出了“开始”通知。
数据流:进去的是工具开始通知,里面有调用编号和工具名 → 函数复制共享记录列表的引用,做出一条 Start 记录,并在异步任务里把它塞进列表 → 出来没有业务返回值,但 records 这个共享列表多了一条开始记录。
调用关系:它实现扩展系统的工具生命周期接口。dispatch_notifies_tool_lifecycle_contributors 运行工具时,注册表会在执行前触发它。
调用图:外部调用 2 个(clone, pin)。
ToolLifecycleRecorder::on_tool_finish123–139 ↗
fn on_tool_finish(
&'a self,
input: codex_extension_api::ToolFinishInput<'a>,
) -> codex_extension_api::ToolLifecycleFuture<'a>
作用:记录某个工具执行结束这件事,包括它是成功、失败,还是成功但业务结果为 false。它用来验证结束通知没有丢信息。
数据流:进去的是工具结束通知,包含调用编号、工具名和 outcome 结果 → 函数做出一条 Finish 记录,并异步写入共享记录列表 → 出来没有普通返回值,但 records 列表新增一条结束记录。
调用关系:它和 on_tool_start 成对工作。dispatch_notifies_tool_lifecycle_contributors 里,工具执行完以后注册表会触发它,然后测试读取 records 来核对顺序和内容。
调用图:外部调用 2 个(clone, pin)。
handler_looks_up_namespaced_aliases_explicitly143–179 ↗
fn handler_looks_up_namespaced_aliases_explicitly()
作用:测试注册表查工具时必须精确匹配名字,不能把普通名字和带命名空间的名字混在一起。命名空间可以理解成“文件夹前缀”,用来区分不同来源的同名工具。
数据流:进去没有外部输入 → 测试创建一个普通工具名、一个带命名空间的工具名、两个对应处理器,并放入注册表 → 查询三个名字:普通名、正确命名空间名、错误命名空间名 → 最后断言前两个能找到且分别是正确处理器,第三个找不到。
调用关系:这是测试运行器直接执行的单元测试。它主要验证 ToolRegistry::new 建出的注册表和 registry.tool 的查找行为。
调用图:调用 3 个内部函数(new, namespaced, plain);外部调用 5 个(clone, new, from, assert!, assert_eq!)。
function_tools_expose_default_hook_payloads_and_rewrites182–225 ↗
async fn function_tools_expose_default_hook_payloads_and_rewrites() -> anyhow::Result<()>
作用:测试普通函数工具会给钩子系统提供默认的调用前、调用后数据,并且钩子改写输入后,工具调用仍保持函数工具的形状。
数据流:进去没有外部输入 → 测试创建会话、回合上下文、一个带命名空间的 echo 工具调用,以及一段输出 echoed → 检查调用前 payload 里有工具名和 JSON 输入,调用后 payload 里有调用编号、输入和输出 → 再把输入改成 rewritten,确认新的 ToolInvocation 里 arguments 真的变成改写后的 JSON 字符串。
调用关系:测试运行器执行它。它使用 make_session_and_context 准备假会话,用 test_invocation 组装工具调用,并调用 TestHandler 的默认钩子相关方法来验证行为。
调用图:调用 4 个内部函数(make_session_and_context, from_text, test_invocation, namespaced);外部调用 4 个(new, assert_eq!, panic!, json!)。
function_hook_input_defaults_empty_arguments_to_object228–248 ↗
async fn function_hook_input_defaults_empty_arguments_to_object()
作用:测试函数工具的参数如果只是空白字符串,钩子系统会把它当成空对象 {},而不是报错或传出空字符串。
数据流:进去没有外部输入 → 创建一个 echo 工具调用,把 arguments 设成只包含空格 → 调用 pre_tool_use_payload → 出来应是一个带空 JSON 对象的调用前钩子数据。
调用关系:测试运行器执行它。它复用 make_session_and_context 和 test_invocation 搭测试环境,重点检查 TestHandler 默认钩子输入解析的兜底行为。
调用图:调用 3 个内部函数(make_session_and_context, test_invocation, plain);外部调用 2 个(new, assert_eq!)。
spawn_agent_function_tools_use_agent_matcher_alias251–288 ↗
async fn spawn_agent_function_tools_use_agent_matcher_alias()
作用:测试 spawn_agent 工具不管是普通名字还是新版命名空间名字,给钩子看的名字都统一成同一个“代理工具”别名。这样钩子规则不用写两份。
数据流:进去没有外部输入 → 创建同一个会话和回合上下文,分别构造 plain spawn_agent 和 namespaced spawn_agent 两次调用 → 对每次调用生成 pre hook payload → 出来的两个 payload 都应使用 HookToolName::spawn_agent,并保留相同输入 message。
调用关系:测试运行器执行它。它用 TestHandler 模拟两种名字的函数工具,验证默认钩子命名规则里针对 spawn_agent 的特殊兼容逻辑。
调用图:调用 3 个内部函数(make_session_and_context, namespaced, plain);外部调用 2 个(new, assert_eq!)。
code_mode_wait_does_not_expose_default_hook_payloads291–304 ↗
async fn code_mode_wait_does_not_expose_default_hook_payloads()
作用:测试 CodeModeWaitHandler 这个内部等待工具不会自动暴露调用前和调用后钩子数据。也就是说,某些内部工具不会像普通函数工具那样对外发默认钩子信息。
数据流:进去没有外部输入 → 创建 wait 工具调用和一个成功输出 → 分别请求调用前 payload 与调用后 payload → 出来都应该是 None,表示没有默认钩子数据。
调用关系:测试运行器执行它。它直接使用真实的 CodeModeWaitHandler,并用 test_invocation 组装调用,确认这个特殊处理器覆盖了默认行为。
调用图:调用 3 个内部函数(make_session_and_context, from_text, test_invocation);外部调用 2 个(new, assert_eq!)。
write_stdin_does_not_expose_default_pre_tool_use_payload307–319 ↗
async fn write_stdin_does_not_expose_default_pre_tool_use_payload()
作用:测试 WriteStdinHandler 这个写入标准输入的工具不会暴露默认的调用前钩子数据。标准输入可以理解成给正在运行的程序“打字输入”的通道。
数据流:进去没有外部输入 → 创建 WriteStdinHandler 的一次工具调用 → 请求 pre_tool_use_payload → 出来应是 None,表示调用前没有默认钩子数据。
调用关系:测试运行器执行它。它使用真实 WriteStdinHandler,并借助 test_invocation 造出调用对象,检查这个工具的特殊钩子策略。
调用图:调用 2 个内部函数(make_session_and_context, test_invocation);外部调用 2 个(new, assert_eq!)。
post_tool_use_feedback_output_keeps_code_mode_result_typed322–371 ↗
fn post_tool_use_feedback_output_keeps_code_mode_result_typed()
作用:测试钩子反馈给模型看的文本,不能破坏代码模式需要的原始结构化结果。简单说,给人看的话可以变,但机器要用的 JSON 还得保留。
数据流:进去没有外部输入 → 构造一个 AnyToolResult,里面 original 是结构化 JSON { typed: true },model_visible 是文本“hook feedback” → 第一次转换成模型响应时,出来的是文本反馈 → 第二次读取 code_mode_result 时,出来的是原始 JSON。
调用关系:测试运行器执行它。它验证 AnyToolResult、PostToolUseFeedbackOutput 和输出转换之间的配合,确保两条用途不同的结果通道不会互相覆盖。
调用图:调用 2 个内部函数(from_text, new);外部调用 3 个(new, assert_eq!, json!)。
dispatch_notifies_tool_lifecycle_contributors374–452 ↗
async fn dispatch_notifies_tool_lifecycle_contributors() -> anyhow::Result<()>
作用:测试注册表调度工具时,会通知生命周期贡献者:工具何时开始、何时结束、结束结果是什么。生命周期贡献者就是扩展系统里“旁听并记录工具执行过程”的组件。
数据流:进去没有外部输入 → 创建会话和回合上下文,安装 ToolLifecycleRecorder,注册一个会返回 success=false 的工具和一个会报错的工具 → 分别通过 registry.dispatch_any 调度它们 → 成功工具正常返回,失败工具返回 handler failed → 最后从共享记录列表取出记录,确认顺序是开始、结束、开始、结束,且 outcome 正确。
调用关系:测试运行器执行它。它把 ToolLifecycleRecorder 接到扩展注册表里,用 LifecycleTestHandler 制造成功和失败,再通过 ToolRegistry 的调度流程验证生命周期通知链路。
调用图:调用 4 个内部函数(make_session_and_context, new, test_invocation, plain);外部调用 9 个(clone, new, from, new, assert_eq!, new, panic!, new, vec!)。
test_invocation454–474 ↗
fn test_invocation(
session: Arc<crate::session::session::Session>,
turn: Arc<crate::session::turn_context::TurnContext>,
call_id: &str,
tool_name: codex_tools::ToolName,
) -> ToolInvo
作用:快速组装一份测试用的工具调用对象。它像测试里的“假订单生成器”,让每个测试不用重复填写会话、调用编号、工具名等字段。
数据流:进去的是会话、回合上下文、调用编号和工具名 → 函数新建取消令牌、变更跟踪器、默认来源和默认函数参数 {} → 出来是一份完整的 ToolInvocation。
调用关系:多个测试都会调用它,包括函数钩子测试、特殊工具钩子测试和生命周期调度测试。它不执行工具,只负责把后续流程需要的输入对象准备好。
调用图:调用 1 个内部函数(new);被 5 处调用(code_mode_wait_does_not_expose_default_hook_payloads, dispatch_notifies_tool_lifecycle_contributors, function_hook_input_defaults_empty_arguments_to_object, function_tools_expose_default_hook_payloads_and_rewrites, write_stdin_does_not_expose_default_pre_tool_use_payload);外部调用 3 个(new, new, new)。
core/src/tools/router_tests.rs源码 ↗
可以把 ToolRouter 想成前台接线员:模型说“我要用某个工具”,它要判断这个工具叫什么、属于哪个柜台、能不能同时跑、最后该交给谁执行。这个测试文件用一些假的工具来模拟真实情况,比如 MCP 工具、动态工具、扩展提供的工具。它重点验证几件事:本地工具和带命名空间的远程工具不会混淆;工具名里带 namespace(命名空间,也就是“这个工具属于哪一组”)时会被正确保存;只有明确声明支持并行的工具才会被当成能并行;延迟加载的动态工具不会提前展示给模型;扩展插件提供的工具不但能被模型看到,也能真的执行并返回结果。文件里还做了一个“回声”扩展工具,收到什么参数就把参数、调用编号和会话历史原样吐回来,方便测试路由是否完整传递了上下文。
ExtensionEchoContributor::tools39–45 ↗
fn tools(
&self,
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>>
作用:这个函数告诉扩展系统:当前这个测试扩展提供了一个工具执行器。这里提供的是一个假的 echo 工具,用来在测试里验证扩展工具能不能被路由器发现和调用。
数据流:输入是扩展系统给的会话级数据和线程级数据,但这个测试函数不使用它们。它直接创建一个 ExtensionEchoExecutor,包进 Arc(引用计数指针,方便多处共享同一个对象)里,再放进列表返回。结果就是:扩展注册表能看到一个可用的测试工具。
调用关系:extension_tool_test_registry 会把 ExtensionEchoContributor 注册进去;之后 extension_tool_executors_are_model_visible_and_dispatchable 通过这个注册表拿到工具执行器,再交给 ToolRouter 测试展示和派发。
调用图:外部调用 1 个(vec!)。
ExtensionEchoExecutor::tool_name51–53 ↗
fn tool_name(&self) -> ToolName
作用:这个函数给测试扩展工具起一个完整名字。它声明这个工具属于 extension/ 这个命名空间,工具本名叫 echo。
数据流:它不读取外部输入。函数把命名空间 extension/ 和工具名 echo 组合成一个 ToolName 返回。返回值用于让路由器知道“这个执行器对应哪一个工具调用”。
调用关系:ToolRouter 在整理扩展工具或派发调用时会依赖执行器报告自己的名字。这个名字必须和测试里构造的 FunctionCall 的 namespace 和 name 对得上,否则调用就找不到执行器。
调用图:调用 1 个内部函数(namespaced)。
ExtensionEchoExecutor::spec55–76 ↗
fn spec(&self) -> ToolSpec
作用:这个函数描述 echo 工具长什么样,方便模型知道它能怎么调用。这里的描述包括:它在 extension/ 组里,需要一个 message 字符串参数。
数据流:它不接收业务输入。它构造一个工具说明书 ToolSpec:包含命名空间名、命名空间说明、工具名 echo、工具描述、严格参数校验,以及一个 JSON 参数格式。返回后,路由器可以把这份说明展示给模型。
调用关系:extension_tool_executors_are_model_visible_and_dispatchable 会间接检查这份 spec 是否出现在 router.model_visible_specs() 里。也就是说,这个函数支撑“扩展工具能被模型看见”的测试。
调用图:外部调用 3 个(default_namespace_description, Namespace, vec!)。
ExtensionEchoExecutor::handle78–80 ↗
fn handle(&self, call: ExtensionToolCall) -> codex_tools::ToolExecutorFuture<'_>
作用:这个函数是 echo 工具真正被调用时的入口。它把同步接口要求的返回形式,转成一个异步任务。
数据流:输入是一条 ExtensionToolCall,也就是一次扩展工具调用,里面有参数、调用编号和会话信息。函数不直接处理内容,而是把调用交给 handle_call,并把这个异步处理过程打包返回。最终调用方会等待这个任务得到工具输出或错误。
调用关系:ToolRouter 派发到这个扩展执行器时会调用 handle。handle 再把实际工作交给 ExtensionEchoExecutor::handle_call,这样符合工具执行器接口需要的异步调用方式。
调用图:调用 1 个内部函数(handle_call);外部调用 1 个(pin)。
ExtensionEchoExecutor::handle_call84–96 ↗
async fn handle_call(
&self,
call: ExtensionToolCall,
) -> Result<Box<dyn codex_tools::ToolOutput>, codex_tools::FunctionCallError>
作用:这个函数执行假的 echo 工具:把收到的参数和上下文整理成 JSON 返回。它用来证明路由器不仅找到了工具,还把调用参数、调用编号和会话历史都传对了。
数据流:输入是一条扩展工具调用。函数先从调用中取出函数字符串参数,再按 JSON 解析成值;接着把解析出的 arguments、callId、conversationHistory 和 ok:true 放进一个 JSON 输出对象;最后返回这个工具输出。它不改数据库,只产出测试用的响应。
调用关系:它只被 ExtensionEchoExecutor::handle 调用。extension_tool_executors_are_model_visible_and_dispatchable 最后会解析这个输出,确认里面确实包含传入参数、调用 ID 和之前记录的会话历史。
调用图:调用 1 个内部函数(new);被 1 处调用(handle);外部调用 4 个(new, function_arguments, json!, from_str)。
extension_tool_test_registry99–103 ↗
fn extension_tool_test_registry() -> Arc<ExtensionRegistry<Config>>
作用:这个函数搭建一个测试用的扩展注册表。注册表可以理解成“扩展工具名单”,这里面只放一个 echo 测试工具。
数据流:它先创建 ExtensionRegistryBuilder,再把 ExtensionEchoContributor 加进去,最后 build 成 ExtensionRegistry 并用 Arc 包起来返回。输出就是一个可共享的扩展注册表,供测试 session 使用。
调用关系:extension_tool_executors_are_model_visible_and_dispatchable 会调用它,把 session.services.extensions 换成这个测试注册表。这样后面的路由器才能发现扩展提供的 echo 工具。
调用图:调用 1 个内部函数(new);被 1 处调用(extension_tool_executors_are_model_visible_and_dispatchable);外部调用 1 个(new)。
parallel_support_does_not_match_namespaced_local_tool_names106–148 ↗
async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow::Result<()>
作用:这个测试确认:本地工具支持并行,不代表同名的“带命名空间工具”也支持并行。它防止路由器只看短名字,误把远程工具当成本地工具。
数据流:测试先创建一个会话和回合上下文,加载 MCP 工具列表,并建立 ToolRouter。它从 exec_command 和 shell_command 里找一个本地并行工具;然后故意构造一个名字相同但带 mcp__server__ 命名空间的调用。结果应是:本地短名字能匹配并行规则,但带命名空间的假远程调用不能被判定为并行。
调用关系:这个测试直接验证 ToolRouter::tool_supports_parallel 的边界行为。它和 mcp_parallel_support_uses_handler_data 互补:一个防止本地规则误伤命名空间工具,另一个确认真正的 MCP 工具会按自己的信息判断。
调用图:调用 2 个内部函数(make_session_and_context, from_turn_context);外部调用 3 个(default, new, assert!)。
build_tool_call_uses_namespace_for_registry_name151–177 ↗
async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()>
作用:这个测试确认:模型发来的函数调用如果带 namespace,路由器会把 namespace 合进工具名里。这样调用才能找到正确的注册工具,而不是只按短名字乱找。
数据流:输入是一条 ResponseItem::FunctionCall,名字是 create_event,命名空间是 mcp__codex_apps__calendar,参数是空 JSON。测试调用 ToolRouter::build_tool_call,把模型消息转成内部 ToolCall。然后检查输出里的 tool_name 是否带上命名空间、call_id 是否保留、arguments 是否原样保留。
调用关系:它专门盯住 ToolRouter::build_tool_call 这一层转换。后续真正派发工具时,路由器会依赖这里生成的 ToolName 去查执行器。
调用图:调用 1 个内部函数(build_tool_call);外部调用 2 个(assert_eq!, panic!)。
mcp_parallel_support_uses_handler_data180–226 ↗
async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()>
作用:这个测试确认:MCP 工具能不能并行,取决于它自己的工具信息,而不是只看工具短名。MCP 可以理解成外部工具服务器协议,多个服务器可能都有同名工具。
数据流:测试创建两个 MCP 工具信息:它们短名都叫 query_with_delay,但来自不同命名空间;第一个声明支持并行,第二个声明不支持。然后建立 ToolRouter,分别构造两个调用。结果是第一个调用返回支持并行,第二个调用返回不支持并行。
调用关系:它使用 mcp_tool_info 生成测试用 ToolInfo,再把这些信息交给 ToolRouter::from_turn_context。测试目标是 ToolRouter::tool_supports_parallel 是否根据完整的 MCP handler 数据判断。
调用图:调用 3 个内部函数(make_session_and_context, from_turn_context, namespaced);外部调用 4 个(default, new, assert!, vec!)。
tools_without_handlers_do_not_support_parallel229–252 ↗
async fn tools_without_handlers_do_not_support_parallel() -> anyhow::Result<()>
作用:这个测试确认:没有执行器或处理信息的工具,默认不能并行。这样做更安全,避免系统在不知道工具是否安全的情况下同时运行多次。
数据流:测试创建一个没有 MCP 工具、没有扩展执行器的 ToolRouter。然后构造一个 web_search 工具调用,询问它是否支持并行。结果必须是否定的。
调用关系:它测试 ToolRouter::tool_supports_parallel 的保守默认值。和其他并行测试一起,覆盖“本地工具”“MCP 工具”“无处理器工具”三种场景。
调用图:调用 2 个内部函数(make_session_and_context, from_turn_context);外部调用 3 个(default, new, assert!)。
specs_filter_deferred_dynamic_tools255–304 ↗
async fn specs_filter_deferred_dynamic_tools() -> anyhow::Result<()>
作用:这个测试确认:标记为 defer_loading(延迟加载)的动态工具,不会一开始就展示给模型。这样可以避免模型看到还没准备好、或需要先发现后才能用的工具。
数据流:测试手工创建一个动态工具命名空间 codex_app,里面有两个函数:hidden_dynamic_tool 标记延迟加载,visible_dynamic_tool 标记立即可见。把它们交给 ToolRouter 后,测试读取 model_visible_specs,并提取 codex_app 下的函数名。结果应该只剩 visible_dynamic_tool。
调用关系:它通过 namespace_function_names 辅助读取可见工具名,核心是在验证 ToolRouter::from_turn_context 构建展示清单时会过滤延迟动态工具。
调用图:调用 2 个内部函数(make_session_and_context, from_turn_context);外部调用 4 个(default, new, assert_eq!, vec!)。
mcp_tool_info306–330 ↗
fn mcp_tool_info(
server_name: &str,
supports_parallel_tool_calls: bool,
callable_namespace: &str,
tool_name: &str,
) -> codex_mcp::ToolInfo
作用:这个辅助函数快速造一个测试用的 MCP 工具资料。它避免每个测试都重复写一大段 ToolInfo 初始化代码。
数据流:输入包括服务器名、是否支持并行、可调用命名空间和工具名。函数把这些值填进 codex_mcp::ToolInfo,并补上测试说明、简单 JSON 参数结构、空的连接器和插件信息。输出是一份完整的假 MCP 工具信息。
调用关系:mcp_parallel_support_uses_handler_data 调用它来制造两个同名但不同服务器、不同并行能力的工具。它本身不测试业务,只是给测试准备材料。
extension_tool_executors_are_model_visible_and_dispatchable333–416 ↗
async fn extension_tool_executors_are_model_visible_and_dispatchable() -> anyhow::Result<()>
作用:这个测试验证扩展工具的完整链路:模型能看到它,路由器能识别调用,执行器能运行,并且输出里带着正确上下文。它是这个文件里最接近真实使用流程的测试。
数据流:测试先创建会话和回合,把会话的扩展注册表换成只含 echo 工具的测试注册表;再记录一条用户历史消息。然后用 extension_tool_executors 取出扩展执行器,建立 ToolRouter。测试先检查模型可见工具列表里有 extension/echo;再构造一次 echo 函数调用,交给 dispatch_tool_call_with_code_mode_result 执行。最后把响应解析成 JSON,确认参数、callId、conversationHistory 和 ok:true 都正确。
调用关系:它串起 extension_tool_test_registry、ExtensionEchoContributor::tools、ExtensionEchoExecutor::spec、ToolRouter::build_tool_call、ToolRouter::from_turn_context 和实际派发流程。ExtensionEchoExecutor::handle 最终会调用 handle_call 生成测试输出。
调用图:调用 5 个内部函数(make_session_and_context, build_tool_call, from_turn_context, extension_tool_test_registry, new);外部调用 12 个(new, new, default, assert!, assert_eq!, json!, panic!, from_str, from_ref, extension_tool_executors (+2 more))。
namespace_function_names418–439 ↗
fn namespace_function_names(specs: &[ToolSpec], namespace_name: &str) -> Vec<String>
作用:这个辅助函数从一堆工具说明里,找出某个命名空间下所有函数工具的名字。它让测试不用手写复杂的匹配代码。
数据流:输入是一组 ToolSpec 和一个命名空间名字。函数逐个查看工具说明,找到名字匹配的 Namespace 后,遍历里面的工具,把函数工具的 name 收集成字符串列表返回。如果没找到,就返回空列表。
调用关系:specs_filter_deferred_dynamic_tools 用它读取 codex_app 命名空间里当前对模型可见的函数名。这样测试可以专心比较“隐藏工具是否被过滤掉”。
调用图:外部调用 1 个(iter)。
core/src/tools/spec_plan_tests.rs源码 ↗
项目会根据很多条件给模型准备工具,比如能不能联网搜索、能不能开 shell、是否启用多智能体、有没有 MCP 外部工具、是不是 ChatGPT 登录等。这个文件不真正执行这些工具,而是搭出各种“假场景”,生成工具路由器,再检查模型能看到的工具、后台已注册的工具、工具暴露方式是否符合预期。可以把它理解成餐厅菜单测试:不同客人、不同权限、不同厨房设备下,菜单上该出现什么、不该出现什么,都要验清楚。文件里有一些小帮手,用来快速改功能开关、换认证方式、造假的 MCP 工具、造动态工具和插件;还有两个假扩展工具,用来测试“可搜索但暂不展示”的工具。重点是保证工具规划稳定,避免模型拿到不该拿的能力,或者该用的能力被漏掉。
ToolPlanProbe::from_router58–105 ↗
fn from_router(router: ToolRouter) -> Self
作用:把一个已经建好的工具路由器拆开,整理成测试更容易检查的清单。它会收集模型可见工具、后台注册工具、命名空间里的函数,以及每个工具的暴露方式。
数据流:输入是一个 ToolRouter(工具路由器)→ 它读取模型可见的工具规格和测试用的注册工具名,再按名字整理成列表和映射表 → 输出一个 ToolPlanProbe,后面的测试就用它来问“有没有这个工具”。
调用关系:probe_with 建好路由器后会调用它;tool_search_cache_rebuilds_when_deferred_sources_change 也直接用它比较两次工具搜索结果。它依赖路由器提供 model_visible_specs 和 registered_tool_names_for_test 这些测试视角的数据。
调用图:调用 2 个内部函数(model_visible_specs, registered_tool_names_for_test);被 2 处调用(probe_with, tool_search_cache_rebuilds_when_deferred_sources_change)。
ToolPlanProbe::assert_visible_contains107–115 ↗
fn assert_visible_contains(&self, expected: &[&str])
作用:检查某些工具确实出现在“模型看得见”的工具列表里。测试用它确认该给模型的能力没有漏掉。
数据流:输入是一组期望出现的工具名 → 它逐个到 visible_names 里查找 → 找不到就让测试失败,并打印当前可见工具列表。
调用关系:很多测试在生成 ToolPlanProbe 后都会调用它,作为最终验收步骤;它不再把工作交给别的业务函数,只用断言表达测试要求。
调用图:外部调用 1 个(assert!)。
ToolPlanProbe::assert_visible_lacks117–125 ↗
fn assert_visible_lacks(&self, expected_absent: &[&str])
作用:检查某些工具没有出现在模型可见列表里。它用来防止模型看到被功能开关、权限或环境禁止的工具。
数据流:输入是一组不该出现的工具名 → 它逐个检查 visible_names → 如果发现其中任何一个已经可见,就让测试失败。
调用关系:各个测试用它验证隐藏规则,比如关闭搜索、没有环境、使用特定 provider 时工具不应出现。
调用图:外部调用 1 个(assert!)。
ToolPlanProbe::assert_registered_contains127–137 ↗
fn assert_registered_contains(&self, expected: &[&str])
作用:检查某些工具已经在后台注册。也就是说,工具可能不展示给模型,但系统仍知道怎么调用它。
数据流:输入是一组期望注册的工具名 → 它逐个检查 registered_names → 缺少任何一个都会让测试失败。
调用关系:测试用它区分“可见”和“已注册”这两个概念,特别是隐藏工具、延迟加载工具、命名空间工具的场景。
调用图:外部调用 1 个(assert!)。
ToolPlanProbe::assert_registered_lacks139–150 ↗
fn assert_registered_lacks(&self, expected_absent: &[&str])
作用:检查某些工具完全没有注册。它用来确认无效工具或被条件挡住的工具不会进入后台。
数据流:输入是一组不该注册的工具名 → 它查看 registered_names → 如果发现这些名字存在,就让测试失败。
调用关系:测试在验证无环境、无效 MCP schema、禁用功能等情况时会用它,确保工具不只是被隐藏,而是真的没进运行时清单。
调用图:外部调用 1 个(assert!)。
ToolPlanProbe::namespace_function_names152–156 ↗
fn namespace_function_names(&self, namespace: &str) -> &[String]
作用:取出某个命名空间下面有哪些函数名。命名空间可以理解成工具分组,比如 agents 下面有 spawn_agent、wait_agent。
数据流:输入是命名空间名字 → 它到 namespace_functions 映射表里查 → 找到就返回里面的函数名切片,找不到就返回空列表。
调用关系:多智能体、MCP、动态工具相关测试用它检查分组工具的内部成员是否正确。
ToolPlanProbe::visible_spec158–163 ↗
fn visible_spec(&self, name: &str) -> &ToolSpec
作用:按名字取出某个模型可见工具的完整规格。测试需要看工具描述、参数 schema(参数格式说明)时会用它。
数据流:输入是工具名 → 它在 visible_specs 中查找同名规格 → 找到就返回引用,找不到就让测试直接失败并打印现有可见工具。
调用关系:许多测试先确认工具可见,再用它深入检查参数、描述或工具类型,比如 exec_command 有没有 shell 参数。
ToolPlanProbe::exposure165–170 ↗
fn exposure(&self, name: &str) -> ToolExposure
作用:查询某个已注册工具的暴露方式。暴露方式说明工具是直接给模型用、隐藏、还是只能通过搜索发现。
数据流:输入是工具名 → 它在 exposures 映射表里查 → 返回 ToolExposure;如果没注册,就让测试失败。
调用关系:测试用它确认 request_user_input、隐藏的 shell_command、延迟扩展工具、多智能体工具等是否以正确方式暴露。
probe_with173–191 ↗
async fn probe_with(
configure_turn: impl FnOnce(&mut TurnContext),
inputs: ToolPlanInputs,
) -> ToolPlanProbe
作用:这是测试里最核心的搭台函数:创建一轮假的会话,按测试要求修改它,再生成工具计划。它让每个测试只关心“我要改哪些条件”。
数据流:输入是一个修改 TurnContext 的闭包和一组额外工具输入 → 它创建测试会话,把闭包作用到 turn 上,再调用 ToolRouter::from_turn_context 生成路由器 → 输出整理好的 ToolPlanProbe。
调用关系:大量测试直接调用它;probe 是它的简化版。它把 make_session_and_context、ToolRouter::from_turn_context 和 ToolPlanProbe::from_router 串起来。
调用图:调用 3 个内部函数(make_session_and_context, from_turn_context, from_router);被 10 处调用(code_mode_only_exposes_code_executor_and_hides_nested_tools, deferred_extension_tools_are_discoverable_with_tool_search, excluded_deferred_namespaces_do_not_enable_nested_tool_guidance, hosted_tools_follow_provider_auth_model_and_config_gates, install_suggestion_tools_stay_visible_without_tool_search, invalid_mcp_tools_are_not_registered, mcp_and_tool_search_follow_direct_and_deferred_tool_exposure, probe, request_plugin_install_description_defers_inventory_to_list_tool, request_plugin_install_requires_all_discovery_features_and_discoverable_tools);外部调用 1 个(default)。
probe193–195 ↗
async fn probe(configure_turn: impl FnOnce(&mut TurnContext)) -> ToolPlanProbe
作用:probe_with 的简化包装,用默认工具输入生成工具计划。适合不需要额外 MCP、扩展或动态工具的测试。
数据流:输入是一个修改 TurnContext 的闭包 → 它补上默认 ToolPlanInputs → 调用 probe_with → 返回 ToolPlanProbe。
调用关系:大部分测试都用它快速进入“改配置、看工具列表”的流程;复杂场景再换用 probe_with。
调用图:调用 1 个内部函数(probe_with);被 19 处调用(code_mode_only_can_expose_namespaced_multi_agent_v2_as_normal_tools, environment_count_controls_environment_backed_tools, host_context_gates_agent_job_tools, hosted_tools_follow_provider_auth_model_and_config_gates, mcp_and_tool_search_follow_direct_and_deferred_tool_exposure, multi_agent_feature_selects_one_agent_tool_family, multi_agent_v2_can_use_configured_tool_namespace, multi_agent_v2_message_schemas_are_encrypted, multi_agent_v2_namespace_is_supported_by_bedrock_provider, request_plugin_install_requires_all_discovery_features_and_discoverable_tools (+9 more));外部调用 1 个(default)。
set_feature197–231 ↗
fn set_feature(turn: &mut TurnContext, feature: Feature, enabled: bool)
作用:打开或关闭某个功能开关,并同步更新本轮上下文里的配置和工具模式。这样测试不会只改一处,导致状态前后不一致。
数据流:输入是 turn、某个 Feature、以及启用还是禁用 → 它同时修改 turn.features 和 turn.config.features,并重新计算 multi_agent_version 与 tool_mode → 输出没有返回值,但 turn 被更新。
调用关系:set_features 会反复调用它;各测试通过 set_features 间接使用它来模拟不同产品功能开关组合。
调用图:被 1 处调用(set_features);外部调用 1 个(new)。
set_features233–237 ↗
fn set_features(turn: &mut TurnContext, features: &[Feature])
作用:一次性打开多个功能开关。它是测试里的省事写法。
数据流:输入是 turn 和一组 Feature → 它逐个调用 set_feature 并设为启用 → turn 中相关功能都被打开。
调用关系:需要同时启用 CodeMode、UnifiedExec、MultiAgentV2 等组合功能的测试会用它,实际细节交给 set_feature。
调用图:调用 1 个内部函数(set_feature)。
zsh_fork_config_for_spec_plan_tests239–252 ↗
fn zsh_fork_config_for_spec_plan_tests() -> codex_tools::ZshForkConfig
作用:造一个测试用的 ZshFork 配置。ZshFork 是一种 shell 运行模式;这里不真的执行,只需要让工具规划认为这种模式存在。
数据流:它读取当前测试程序的可执行文件路径 → 把这个绝对路径当作占位的 zsh 和 wrapper 路径 → 返回 ZshForkConfig。
调用关系:ZshFork 相关测试用它设置 unified_exec_shell_mode;它避免测试依赖真实打包出来的 zsh-fork 文件。
调用图:调用 1 个内部函数(try_from);外部调用 1 个(current_exe)。
update_config254–258 ↗
fn update_config(turn: &mut TurnContext, update: impl FnOnce(&mut crate::config::Config))
作用:安全地修改 turn 里的配置。因为配置放在 Arc(可共享引用)里,所以要先克隆一份再替换。
数据流:输入是 turn 和一个修改 Config 的闭包 → 它克隆当前配置,让闭包改克隆版 → 再把新配置包回 Arc 放回 turn。
调用关系:set_web_search_mode 和 use_bedrock_provider 会调用它;很多测试也通过它改实验开关、多智能体配置、命名空间等。
调用图:被 2 处调用(set_web_search_mode, use_bedrock_provider);外部调用 1 个(new)。
set_web_search_mode260–267 ↗
fn set_web_search_mode(turn: &mut TurnContext, mode: WebSearchMode)
作用:设置网页搜索模式,比如是否允许实时搜索。它让测试可以专门验证 web_search 工具何时出现。
数据流:输入是 turn 和 WebSearchMode → 它通过 update_config 修改 config.web_search_mode → turn 的配置被更新。
调用关系:托管工具相关测试会调用它;具体改配置的动作交给 update_config。
调用图:调用 1 个内部函数(update_config)。
use_chatgpt_auth269–277 ↗
fn use_chatgpt_auth(turn: &mut TurnContext)
作用:把当前测试会话改成 ChatGPT 登录状态。某些托管工具,比如图片生成,需要这种认证才会出现。
数据流:输入是 turn → 它创建一个测试用的 ChatGPT 假认证,放进 auth_manager,再据此重建 provider → turn 的认证和模型提供方被更新。
调用关系:hosted_tools_follow_provider_auth_model_and_config_gates 用它测试图片生成等工具的认证门槛。
调用图:调用 2 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing);外部调用 1 个(create_model_provider)。
use_bedrock_provider279–286 ↗
fn use_bedrock_provider(turn: &mut TurnContext)
作用:把当前测试会话切到 Amazon Bedrock 模型提供方。这样可以测试不同模型服务商支持的工具能力是否不同。
数据流:输入是 turn → 它创建 Bedrock provider 信息,更新配置里的 provider id 和 provider 配置,再重建 turn.provider → turn 变成 Bedrock 场景。
调用关系:MCP 搜索、多智能体命名空间、web_search provider 限制等测试会调用它;更新配置的部分交给 update_config。
调用图:调用 2 个内部函数(update_config, create_amazon_bedrock_provider);外部调用 1 个(create_model_provider)。
WebRunExtensionTool::tool_name291–293 ↗
fn tool_name(&self) -> ToolName
作用:声明这个假扩展工具的名字是 web.run。它用于模拟已经存在一个独立的 web 运行工具。
数据流:没有外部输入 → 它构造一个带命名空间的 ToolName,namespace 是 web,函数名是 run → 返回这个工具名。
调用关系:ToolRouter 读取扩展工具执行器时会调用它,用来判断是否已经有 web.run,从而影响 web_search 是否展示。
调用图:调用 1 个内部函数(namespaced)。
WebRunExtensionTool::spec295–308 ↗
fn spec(&self) -> ToolSpec
作用:给假 web.run 工具提供模型可读的规格说明。这里说明它属于 web 命名空间,里面有 run 函数。
数据流:没有外部输入 → 它构造一个 Namespace 类型的 ToolSpec,包含描述和 run 函数的空参数 schema → 返回这个工具规格。
调用关系:工具规划在收集扩展工具时会读取它;hosted_tools_follow_provider_auth_model_and_config_gates 用这个假工具测试 standalone web search 的替代关系。
调用图:外部调用 2 个(Namespace, vec!)。
WebRunExtensionTool::handle310–314 ↗
DeferredExtensionTool::tool_name320–322 ↗
fn tool_name(&self) -> ToolName
作用:声明这个假扩展工具叫 extension_echo。它用来测试“已注册但延迟展示”的扩展工具。
数据流:没有外部输入 → 它构造一个普通 ToolName → 返回 extension_echo。
调用关系:ToolRouter 收集扩展工具时会读它;deferred_extension_tools_are_discoverable_with_tool_search 用它检查工具搜索能发现延迟工具。
调用图:调用 1 个内部函数(plain)。
DeferredExtensionTool::spec324–340 ↗
DeferredExtensionTool::exposure342–344 ↗
fn exposure(&self) -> ToolExposure
作用:声明这个扩展工具的暴露方式是 Deferred,也就是先不直接给模型看,只能通过工具搜索等方式发现。
数据流:没有外部输入 → 直接返回 ToolExposure::Deferred → 不修改状态。
调用关系:deferred_extension_tools_are_discoverable_with_tool_search 会检查这个结果;ToolRouter 用它决定 extension_echo 的可见性。
DeferredExtensionTool::handle346–348 ↗
fn handle(&self, _call: ExtensionToolCall) -> codex_tools::ToolExecutorFuture<'_>
作用:故意在执行时 panic,因为规格规划测试不应该真的执行这个工具。它像一个报警器,提醒测试走错路了。
数据流:输入是工具调用,但不会被使用 → 一旦运行异步体就 panic → 没有正常输出。
调用关系:它只是满足 ToolExecutor 接口;如果某个测试意外执行了扩展工具,就会立刻失败。
调用图:外部调用 2 个(pin, panic!)。
duplicate_primary_environment351–355 ↗
fn duplicate_primary_environment(turn: &mut TurnContext)
作用:复制当前主运行环境,造出第二个环境。这样测试可以验证多环境时工具是否需要 environment_id 参数。
数据流:输入是 turn → 它克隆第一个 turn environment,把 id 改成 secondary,再推回环境列表 → turn 从单环境变成多环境。
调用关系:environment_count_controls_environment_backed_tools 用它模拟有多个工作区或执行环境的情况。
mcp_tool357–378 ↗
fn mcp_tool(server: &str, namespace: &str, name: &str) -> ToolInfo
作用:造一个测试用的 MCP 工具。MCP 可以理解成外部工具服务协议;这里生成一份合法的工具信息。
数据流:输入是 server、namespace、name → 它填好服务器名、命名空间、工具名、描述和一个空对象参数 schema → 返回 ToolInfo。
调用关系:MCP 相关测试用它创建直接工具或延迟工具;invalid_mcp_tool 也先调用它再故意改坏 schema。
调用图:被 1 处调用(invalid_mcp_tool);外部调用 6 个(new, new, format!, json!, new, object)。
invalid_mcp_tool380–386 ↗
fn invalid_mcp_tool(server: &str, namespace: &str, name: &str) -> ToolInfo
作用:造一个故意无效的 MCP 工具,用来测试系统会拒绝坏工具。它把输入 schema 改成不合适的 null 类型。
数据流:输入是 server、namespace、name → 它先用 mcp_tool 生成正常工具,再把 input_schema 改坏 → 返回无效 ToolInfo。
调用关系:invalid_mcp_tools_are_not_registered 调用它,确认无效 MCP 工具不会显示也不会注册。
dynamic_tool388–411 ↗
fn dynamic_tool(namespace: Option<&str>, name: &str, defer_loading: bool) -> DynamicToolSpec
作用:造一个测试用动态工具。动态工具是运行时传进来的工具规格,可以是普通函数,也可以放在命名空间里。
数据流:输入是可选 namespace、工具名、是否延迟加载 → 它创建 DynamicToolFunctionSpec;如果有 namespace 就包成 Namespace,否则包成 Function → 返回 DynamicToolSpec。
调用关系:代码模式和排除命名空间相关测试用它模拟外部传入工具,检查这些工具在 code mode only 下如何展示或隐藏。
discoverable_plugin413–424 ↗
has_parameter426–431 ↗
fn has_parameter(spec: &ToolSpec, parameter_name: &str) -> bool
作用:检查某个工具规格里有没有指定参数。测试用它看工具 schema 里是否暴露 shell、environment_id 等字段。
数据流:输入是 ToolSpec 和参数名 → 它把规格序列化成 JSON,再查 /parameters/properties/参数名 这个位置 → 返回 true 或 false。
调用关系:shell、环境、view_image 等测试用它做细粒度断言,确认工具不只是出现,而且参数形状也对。
apply_patch_accepts_environment_id433–440 ↗
fn apply_patch_accepts_environment_id(spec: &ToolSpec) -> bool
作用:检查 apply_patch 这个补丁工具的自由格式说明里是否提到了 Environment ID。多环境时这很重要,否则模型不知道补丁要打到哪个环境。
数据流:输入是 ToolSpec → 如果它是名为 apply_patch 的 Freeform 工具,就检查格式定义文本是否包含 Environment ID → 返回布尔值。
调用关系:environment_count_controls_environment_backed_tools 用它确认多环境下 apply_patch 的说明会提示环境选择。
request_user_input_tool_respects_experimental_config_gate443–460 ↗
async fn request_user_input_tool_respects_experimental_config_gate()
作用:测试 request_user_input 工具是否受实验配置开关控制。这个工具用于让模型向用户要补充信息,所以不能在关闭时漏出来。
数据流:先用默认配置生成计划 → 检查工具可见、已注册且只允许直接模型使用;再关闭 experimental_request_user_input_enabled → 重新生成计划并确认它不可见也未注册。
调用关系:它通过 probe 搭建两种场景,并用 ToolPlanProbe 的检查函数和 assert_eq 验证暴露方式。
调用图:调用 1 个内部函数(probe);外部调用 1 个(assert_eq!)。
request_user_input_stays_direct_in_code_mode_only463–484 ↗
async fn request_user_input_stays_direct_in_code_mode_only()
作用:测试在 code mode only 下,request_user_input 仍然作为直接工具存在,而不会被塞进代码执行工具的说明里。
数据流:输入场景是启用 CodeMode 和 CodeModeOnly → 生成工具计划 → 检查 request_user_input、exec、wait 可见,并确认 request_user_input 的暴露方式仍是 DirectModelOnly。
调用关系:它调用 probe 和 set_features,随后读取 code mode 的 exec 工具规格,确认描述里没有混入 request_user_input。
调用图:调用 1 个内部函数(probe);外部调用 3 个(assert!, assert_eq!, panic!)。
shell_zsh_fork_stays_standalone_until_unified_exec_composition_is_enabled503–540 ↗
async fn shell_zsh_fork_stays_standalone_until_unified_exec_composition_is_enabled()
作用:测试 ZshFork shell 模式在组合开关没打开前仍保持独立 shell_command;组合开关打开后才可能合入统一执行工具。
数据流:先启用 ShellTool、UnifiedExec、ShellZshFork,但关闭 UnifiedExecZshFork → 检查只出现 shell_command;再打开 UnifiedExecZshFork → 如果系统支持伪终端,就检查 exec_command/write_stdin 可见且 shell_command 隐藏,否则仍保持 shell_command。
调用关系:它调用 probe 生成两个计划,并根据 conpty_supported 这个平台能力决定预期。
调用图:调用 1 个内部函数(probe);外部调用 2 个(assert_eq!, conpty_supported)。
zsh_fork_unified_exec_hides_shell_parameter543–565 ↗
async fn zsh_fork_unified_exec_hides_shell_parameter()
作用:测试 ZshFork 已合入统一执行工具时,如果只有本地环境,exec_command 不需要让模型选择 shell。
数据流:如果平台不支持伪终端就直接跳过 → 否则启用相关功能并设置 ZshFork 配置 → 生成计划后确认 exec_command/write_stdin 可见,且 exec_command 没有 shell 参数。
调用关系:它使用 zsh_fork_config_for_spec_plan_tests 提供占位配置,再用 has_parameter 做参数检查。
调用图:调用 1 个内部函数(probe);外部调用 2 个(assert!, conpty_supported)。
zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_available568–613 ↗
async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_available()
作用:测试有远程环境时,即使本地是 ZshFork,exec_command 仍要保留 shell 和 environment_id 参数。因为不同环境可能需要不同执行方式。
数据流:平台不支持伪终端就跳过 → 启用统一执行和 ZshFork,添加一个远程测试环境 → 生成计划 → 检查 exec_command/write_stdin 可见,并带 shell、environment_id 参数。
调用关系:它在 probe 的配置闭包里手动添加 TurnEnvironment,然后用 has_parameter 验证多环境参数。
调用图:调用 1 个内部函数(probe);外部调用 2 个(assert!, conpty_supported)。
environment_count_controls_environment_backed_tools616–655 ↗
async fn environment_count_controls_environment_backed_tools()
作用:测试依赖运行环境的工具会不会根据环境数量正确出现。没有环境时不能给模型 shell、补丁、看图等工具;多环境时要带环境选择。
数据流:先清空环境并启用相关工具 → 检查 shell、exec、apply_patch、view_image 都不可见也未注册;再复制出第二个环境并启用统一执行和补丁工具 → 检查工具可见且带 environment_id 相关能力。
调用关系:它调用 duplicate_primary_environment、has_parameter 和 apply_patch_accepts_environment_id 来验证环境对工具规格的影响。
host_context_gates_agent_job_tools658–673 ↗
async fn host_context_gates_agent_job_tools()
作用:测试 agent job 相关工具是否受当前会话来源控制。普通会话只能发起 CSV agent 任务,worker 子代理还可以汇报结果。
数据流:先在普通会话启用 SpawnCsv → 检查 spawn_agents_on_csv 可见但 report_agent_job_result 不可见;再把 session_source 改成特定 SubAgent → 检查汇报结果工具也出现。
调用关系:它通过 probe 改变会话上下文,用可见性检查确认 host context 的门禁生效。
调用图:调用 1 个内部函数(probe)。
sleep_tool_follows_feature_gate676–688 ↗
async fn sleep_tool_follows_feature_gate()
作用:测试 sleep 工具只在 SleepTool 功能开关打开时出现。sleep 工具用于让模型等待一段时间。
数据流:先关闭 SleepTool → 生成计划并确认 sleep 不可见;再打开 SleepTool → 生成计划并确认 sleep 可见。
调用关系:它用 probe 和 set_feature 构造开关两边的场景。
调用图:调用 1 个内部函数(probe)。
mcp_and_tool_search_follow_direct_and_deferred_tool_exposure691–764 ↗
async fn mcp_and_tool_search_follow_direct_and_deferred_tool_exposure()
作用:测试 MCP 工具和 tool_search 的关系。直接 MCP 工具应直接显示,延迟 MCP 工具只有在模型支持搜索且有 deferred 来源时才通过 tool_search 发现。
数据流:先传入直接 MCP 工具 → 检查 MCP 资源工具和命名空间函数可见;再用延迟 MCP 工具分别测试模型不支持搜索、没有延迟工具、Bedrock provider、正常支持搜索等情况 → 检查 tool_search 和注册名是否符合预期。
调用关系:它使用 probe_with、mcp_tool、use_bedrock_provider 和 ToolName::namespaced,覆盖直接暴露、延迟暴露和 provider 能力差异。
调用图:调用 3 个内部函数(probe, probe_with, namespaced);外部调用 3 个(assert_eq!, default, vec!)。
deferred_extension_tools_are_discoverable_with_tool_search767–783 ↗
async fn deferred_extension_tools_are_discoverable_with_tool_search()
作用:测试延迟暴露的扩展工具不会直接显示,但会注册并可通过 tool_search 发现。
数据流:输入场景是模型支持搜索,并传入 DeferredExtensionTool → 生成计划 → 检查 tool_search 可见、extension_echo 不可见但已注册,暴露方式为 Deferred。
调用关系:它调用 probe_with,并把 Arc 包裹的 DeferredExtensionTool 作为扩展执行器传给工具路由器。
调用图:调用 1 个内部函数(probe_with);外部调用 3 个(assert_eq!, default, vec!)。
tool_search_cache_rebuilds_when_deferred_sources_change786–838 ↗
async fn tool_search_cache_rebuilds_when_deferred_sources_change()
作用:测试 tool_search 的缓存不会拿旧数据糊弄新场景。延迟工具来源变化时,搜索工具描述也必须重建。
数据流:创建同一个 ToolSearchHandlerCache → 第一轮放入 first MCP 延迟工具并生成计划;第二轮放入 second MCP 延迟工具并生成计划 → 分别检查 tool_search 描述只包含自己的服务器说明,不混入另一轮内容。
调用关系:它绕过 probe_with,直接调用 make_session_and_context、ToolRouter::from_turn_context 和 ToolPlanProbe::from_router,专门检查缓存行为。
调用图:调用 3 个内部函数(make_session_and_context, from_turn_context, from_router);外部调用 5 个(new, assert!, default, panic!, vec!)。
invalid_mcp_tools_are_not_registered841–853 ↗
async fn invalid_mcp_tools_are_not_registered()
作用:测试 schema 不合法的 MCP 工具会被拒绝。坏工具不应该出现在模型面前,也不应该进入后台注册表。
数据流:传入 invalid_mcp_tool 生成的坏 MCP 工具 → 生成工具计划 → 检查 mcp__invalid 命名空间不可见,对应 namespaced 工具名也未注册。
调用关系:它用 probe_with 传入测试输入,用 invalid_mcp_tool 制造坏数据。
调用图:调用 2 个内部函数(probe_with, namespaced);外部调用 2 个(default, vec!)。
request_plugin_install_requires_all_discovery_features_and_discoverable_tools856–908 ↗
async fn request_plugin_install_requires_all_discovery_features_and_discoverable_tools()
作用:测试插件安装建议工具只有在所有发现功能都打开、并且确实有可安装候选插件时才出现。
数据流:先准备一个可发现插件 → 分别关闭 ToolSuggest、Apps、Plugins 中的一个并检查安装相关工具不可见;再测试没有候选插件时也不可见;最后三项功能全开且有候选插件时检查两个工具可见。
调用关系:它使用 discoverable_plugin、set_features、set_feature、probe_with 和 probe 组合多个门禁条件。
调用图:调用 2 个内部函数(probe, probe_with);外部调用 2 个(default, vec!)。
install_suggestion_tools_stay_visible_without_tool_search911–932 ↗
async fn install_suggestion_tools_stay_visible_without_tool_search()
作用:测试插件安装建议工具不依赖 tool_search。即使模型不支持搜索,只要插件发现条件满足,安装建议工具也应直接可见。
数据流:关闭模型搜索能力,打开 ToolSuggest、Apps、Plugins,并传入可发现插件 → 生成计划 → 检查安装建议工具可见,同时 tool_search 不可见。
调用关系:它调用 probe_with 和 discoverable_plugin,验证插件发现工具与工具搜索是两条独立路径。
调用图:调用 1 个内部函数(probe_with);外部调用 2 个(default, vec!)。
request_plugin_install_description_defers_inventory_to_list_tool935–972 ↗
async fn request_plugin_install_description_defers_inventory_to_list_tool()
作用:测试 request_plugin_install 的描述不会直接泄露具体插件库存,而是要求先调用列表工具确认候选项。
数据流:打开插件发现功能并传入 GitHub 插件 → 生成计划 → 读取 list_available_plugins_to_install 和 request_plugin_install 的描述,确认前者说明会返回候选,后者要求先列表匹配且不直接包含 github 字样。
调用关系:它通过 visible_spec 取函数规格,再检查 ResponsesApiTool 的 description 文本。
调用图:调用 1 个内部函数(probe_with);外部调用 4 个(assert!, default, panic!, vec!)。
code_mode_only_exposes_code_executor_and_hides_nested_tools975–1016 ↗
async fn code_mode_only_exposes_code_executor_and_hides_nested_tools()
作用:测试 code mode only 下,模型主要看到代码执行入口,而普通嵌套动态工具会被隐藏起来。
数据流:先传入 codex_app.lookup 动态工具且不开 code mode only → 检查命名空间函数可见;再启用 CodeMode 和 CodeModeOnly,传入同样工具 → 检查 exec/wait 可见,而 codex_app 里的 lookup 不再暴露给模型。
调用关系:它用 dynamic_tool 构造动态工具,用 probe_with 比较普通模式和 code mode only 模式。
调用图:调用 1 个内部函数(probe_with);外部调用 3 个(assert_eq!, default, vec!)。
excluded_deferred_namespaces_do_not_enable_nested_tool_guidance1019–1052 ↗
async fn excluded_deferred_namespaces_do_not_enable_nested_tool_guidance()
作用:测试被配置排除的延迟命名空间,不会让 code mode 的执行工具提示“有些延迟嵌套工具被省略”。
数据流:启用 code mode only,关闭 Collab,打开模型搜索能力,并把 excluded 加入排除命名空间 → 传入 excluded.lookup 延迟动态工具 → 检查 exec 描述没有相关提示,同时工具仍注册且 tool_search 注册。
调用关系:它用 dynamic_tool、update_config 和 ToolName::namespaced,专门覆盖排除命名空间的边界情况。
调用图:调用 2 个内部函数(probe_with, namespaced);外部调用 4 个(assert!, default, panic!, vec!)。
multi_agent_feature_selects_one_agent_tool_family1055–1153 ↗
async fn multi_agent_feature_selects_one_agent_tool_family()
作用:测试多智能体功能一次只选择一套工具家族。旧版 v1 是命名空间工具,新版 v2 是一组普通函数工具,也能在 code mode only 下改为直接模型工具。
数据流:先启用 Collab 并关闭 MultiAgentV2 → 检查 v1 命名空间可见且包含 spawn/resume/wait 等函数和参数;再启用 MultiAgentV2 → 检查 v2 工具可见、旧工具不可见、描述符合预期;最后在 code mode only 加 non_code_mode_only → 检查 spawn_agent 暴露方式为 DirectModelOnly。
调用关系:它通过 probe、set_feature、set_features、update_config 覆盖 v1、v2 和 code mode only 三种多智能体规划。
调用图:调用 1 个内部函数(probe);外部调用 3 个(assert!, assert_eq!, panic!)。
multi_agent_v2_message_schemas_are_encrypted1156–1177 ↗
async fn multi_agent_v2_message_schemas_are_encrypted()
作用:测试多智能体 v2 中包含 message 的工具参数被标记为加密。这样消息内容在协议层会按敏感数据处理。
数据流:启用 MultiAgentV2 → 生成计划 → 对 spawn_agent、send_message、followup_task 逐个取参数 schema → 检查 message 字段的 encrypted 标记是 true。
调用关系:它使用 visible_spec 深入读取工具参数定义,确保多智能体消息工具的安全属性没有丢。
调用图:调用 1 个内部函数(probe);外部调用 2 个(assert_eq!, panic!)。
tool_mode_selector_overrides_feature_flags1180–1191 ↗
async fn tool_mode_selector_overrides_feature_flags()
作用:测试模型信息里的 tool_mode 可以覆盖功能开关。即使 CodeMode 和 CodeModeOnly 开着,如果 tool_mode 指定 Direct,也不应显示 code mode 工具。
数据流:启用 CodeMode 和 CodeModeOnly,同时把 model_info.tool_mode 和 turn.tool_mode 都设为 Direct → 生成计划 → 确认 exec/wait 等 code mode 入口不可见。
调用关系:它用 probe 搭建冲突场景,验证最终工具模式选择器优先级高于功能开关。
调用图:调用 1 个内部函数(probe)。
v1_multi_agent_tools_defer_when_tool_search_available1194–1236 ↗
async fn v1_multi_agent_tools_defer_when_tool_search_available()
作用:测试旧版多智能体工具在 tool_search 可用时会变成延迟工具,而不是直接摊开给模型。
数据流:打开模型搜索能力,启用 Collab,关闭 MultiAgentV2 → 生成计划 → 检查 tool_search 可见,v1 的 spawn/send/resume/wait/close 等不直接可见,但以命名空间形式注册且暴露方式为 Deferred;同时 tool_search 描述包含多智能体工具说明。
调用关系:它调用 probe 和 ToolName::namespaced,验证 v1 多智能体与工具搜索的集成。
调用图:调用 2 个内部函数(probe, namespaced);外部调用 3 个(assert!, assert_eq!, panic!)。
multi_agent_v2_can_use_configured_tool_namespace1239–1292 ↗
async fn multi_agent_v2_can_use_configured_tool_namespace()
作用:测试多智能体 v2 可以被配置到一个命名空间里,比如 agents,而不是作为一堆顶层工具出现。
数据流:启用 MultiAgentV2,并把 tool_namespace 配成 agents → 生成计划 → 检查 agents 命名空间可见,顶层 spawn_agent 等不可见;每个应有工具以 agents.xxx 注册并出现在命名空间函数列表里,assign_task 不出现。
调用关系:它通过 update_config 改 v2 配置,用 registered_names 和 namespace_function_names 检查命名空间化后的工具形态。
multi_agent_v2_namespace_is_supported_by_bedrock_provider1295–1316 ↗
async fn multi_agent_v2_namespace_is_supported_by_bedrock_provider()
作用:测试 Bedrock provider 也支持多智能体 v2 的命名空间工具。不同模型服务商的工具格式能力可能不同,所以要单独确认。
数据流:启用 MultiAgentV2,把命名空间设为 agents,再切到 Bedrock provider → 生成计划 → 检查 agents 可见,顶层多智能体工具不可见,spawn_agent 只以 agents.spawn_agent 注册。
调用关系:它结合 update_config 和 use_bedrock_provider,验证 provider 切换不会破坏命名空间多智能体工具。
code_mode_only_can_expose_namespaced_multi_agent_v2_as_normal_tools1319–1369 ↗
async fn code_mode_only_can_expose_namespaced_multi_agent_v2_as_normal_tools()
作用:测试在 code mode only 下,如果配置允许,多智能体 v2 的命名空间工具仍可作为普通模型工具露出。
数据流:启用 CodeMode、CodeModeOnly、MultiAgentV2,设置 non_code_mode_only 并把命名空间设为 agents → 生成计划 → 检查可见工具顺序包含 exec、wait、request_user_input、agents、web_search,并确认 agents 内有 v2 工具但没有 assign_task。
调用关系:它用 probe 构造完整 code mode only 场景,检查代码入口、用户输入、多智能体和托管 web_search 的组合顺序。
调用图:调用 1 个内部函数(probe);外部调用 2 个(assert!, assert_eq!)。
hosted_tools_follow_provider_auth_model_and_config_gates1372–1467 ↗
async fn hosted_tools_follow_provider_auth_model_and_config_gates()
作用:测试托管工具会同时受认证方式、模型能力、provider 和配置开关控制。托管工具指由模型服务平台提供的能力,比如 web_search、image_generation。
数据流:它依次模拟 API key 认证、ChatGPT 认证、图片能力、扩展图片开关、实时网页搜索、code mode only、standalone web search、已有 web.run 扩展、Bedrock provider 等场景 → 每次生成计划 → 检查 image_generation 和 web_search 是否按条件出现或被隐藏。
调用关系:这是本文件覆盖面最广的测试之一,调用 probe、probe_with、use_chatgpt_auth、use_bedrock_provider、set_web_search_mode,并借助 WebRunExtensionTool 模拟扩展工具冲突。
调用图:调用 2 个内部函数(probe, probe_with);外部调用 3 个(default, assert_eq!, vec!)。
core/src/tools/tool_dispatch_trace_tests.rs源码 ↗
这份测试像是在给工具调度系统装行车记录仪,然后反复检查录像有没有拍全。它先搭一个假的会话和假的工具,再让工具注册表去分发调用:有模型直接发起的调用,也有代码模式里某个代码单元发起的调用;还有故意找不到工具、传错参数格式、等待工具没有对应代码单元这些异常情况。每次调用后,测试都会从临时目录里读回追踪包,确认调用者是谁、调用编号怎么记、执行成功还是失败、原始输入和返回结果有没有保存。这里的 TestHandler 是一个很简单的假工具,只会返回“ok”,目的是排除真实工具的复杂性,专心检查调度和追踪本身。
TestHandler::tool_name34–36 ↗
fn tool_name(&self) -> codex_tools::ToolName
作用:返回这个假工具的名字。测试里的工具注册表靠这个名字判断一次调用应该交给哪个工具。
数据流:进去的是 TestHandler 自己,里面存着一个工具名 → 函数把这个工具名复制一份 → 出来的是可交给注册表使用的工具名,原来的 TestHandler 不变。
调用关系:它是 ToolExecutor 这套接口的一部分。测试把 TestHandler 放进 ToolRegistry 后,注册表在识别和分发工具时会用到这个名字。
调用图:外部调用 1 个(clone)。
TestHandler::spec38–47 ↗
fn spec(&self) -> codex_tools::ToolSpec
作用:生成这个假工具的说明书,告诉系统它叫什么、描述是什么、参数大概是什么样。这里说明书很简单,因为测试重点不是参数校验,而是追踪记录。
数据流:进去的是 TestHandler 中保存的工具名 → 函数把它包装成一个函数型工具的规格说明,填入描述、默认参数结构等信息 → 出来的是 ToolSpec,也就是系统能登记的工具说明。
调用关系:它同样属于 ToolExecutor 接口。ToolRegistry 注册或暴露工具时会需要这份说明,测试借它把假工具装成一个看起来正常的工具。
调用图:外部调用 2 个(default, Function)。
TestHandler::handle49–56 ↗
fn handle(&self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_>
作用:真正执行这个假工具。它不做复杂工作,只返回一段文本“ok”,用来模拟工具成功执行。
数据流:进去的是一次 ToolInvocation,但这个假工具不读取里面的具体参数 → 函数创建一个异步任务,并把“ok”包装成工具输出 → 出来的是成功结果,内容会被调度系统继续拿去写追踪和返回。
调用关系:当 ToolRegistry 找到 TestHandler 并分发调用时,会调用它。它把执行结果交还给调度流程,后面的测试再检查这个结果是否被追踪系统保存了。
dispatch_lifecycle_trace_records_direct_and_code_mode_requesters62–146 ↗
async fn dispatch_lifecycle_trace_records_direct_and_code_mode_requesters() -> anyhow::Result<()>
作用:测试两种正常工具调用都会被正确追踪:一种是模型直接请求工具,另一种是代码模式里的代码单元请求工具。它重点确认系统能分清“谁发起了这次工具调用”。
数据流:先创建临时目录、测试会话和追踪上下文 → 注册一个会返回“ok”的假工具,并制造两次调用:直接调用和代码模式调用 → 调度执行后读回追踪包 → 检查直接调用保留模型可见的调用编号,代码模式调用保留运行时工具编号,并且两者都保存了必要的原始输入或输出。
调用关系:这是主测试之一。它会调用 make_session_and_context 搭环境,调用 attach_test_trace 接上追踪,调用 test_invocation 制造调用对象,再通过 ToolRegistry::dispatch_any 触发真实调度,最后用 single_bundle_dir 找到追踪包并回放检查。
调用图:调用 6 个内部函数(make_session_and_context, with_handler_for_test, attach_test_trace, single_bundle_dir, test_invocation, plain);外部调用 6 个(clone, new, new, assert!, assert_eq!, replay_bundle)。
dispatch_lifecycle_trace_records_unsupported_tool_failures149–176 ↗
async fn dispatch_lifecycle_trace_records_unsupported_tool_failures() -> anyhow::Result<()>
作用:测试调用一个不存在的工具时,系统不但要报错,还要把这次失败写进追踪。这样排查问题时能看见“模型试图调用了一个缺失工具”。
数据流:先创建空工具注册表,也就是没有任何工具可用 → 构造一次名字叫 missing_tool 的调用 → 调度后得到一个会返回给模型的错误 → 再读回追踪包,确认这次工具调用状态是失败,并且失败结果也被保存了。
调用关系:它复用 attach_test_trace 开启追踪,复用 test_invocation 构造调用。它故意让 ToolRegistry::dispatch_any 找不到处理者,用来验证失败路径上的追踪逻辑。
调用图:调用 5 个内部函数(make_session_and_context, empty_for_test, attach_test_trace, single_bundle_dir, test_invocation);外部调用 5 个(new, new, assert!, assert_eq!, replay_bundle)。
dispatch_lifecycle_trace_records_incompatible_payload_failures179–210 ↗
async fn dispatch_lifecycle_trace_records_incompatible_payload_failures() -> anyhow::Result<()>
作用:测试工具调用带了不合适的载荷格式时,系统会把它当成失败并记录下来。这里的“载荷”就是一次调用携带的输入内容和格式。
数据流:先注册一个正常的假工具 → 构造一次工具名正确但 payload 类型不兼容的调用 → 调度系统发现这种输入不能按函数工具方式处理,于是返回严重错误 → 测试读回追踪包,确认执行状态是失败,并且失败结果有保存。
调用关系:它调用 test_invocation_with_payload 来精确制造错误格式的调用,而不是走默认的函数参数格式。这个测试覆盖的是工具存在、但输入包装方式不对的失败分支。
调用图:调用 6 个内部函数(make_session_and_context, with_handler_for_test, attach_test_trace, single_bundle_dir, test_invocation_with_payload, plain);外部调用 5 个(new, new, assert!, assert_eq!, replay_bundle)。
missing_code_mode_wait_traces_only_the_wait_tool_call213–242 ↗
async fn missing_code_mode_wait_traces_only_the_wait_tool_call() -> anyhow::Result<()>
作用:测试代码模式的等待工具在找不到对应代码单元时,不会凭空生成一条代码单元追踪,只记录这次等待工具调用本身。
数据流:先创建会话和追踪,再注册 CodeModeWaitHandler 这个等待工具 → 发起一次 wait-call,参数里给了一个不存在的 cell_id,并要求结束 → 调度完成后读回追踪包 → 检查没有代码单元记录,但这次等待工具的返回结果仍然被保存。
调用关系:它使用真实的 CodeModeWaitHandler,而不是 TestHandler。它通过 test_invocation 构造直接调用,再让 ToolRegistry::dispatch_any 执行,用来验证代码模式等待工具的一个边界情况。
调用图:调用 5 个内部函数(make_session_and_context, with_handler_for_test, attach_test_trace, single_bundle_dir, test_invocation);外部调用 5 个(new, new, assert!, assert_eq!, replay_bundle)。
test_invocation244–262 ↗
fn test_invocation(
session: Arc<Session>,
turn: Arc<TurnContext>,
call_id: &str,
tool_name: &str,
source: ToolCallSource,
arguments: &str,
) -> ToolInvocation
作用:这是测试用的小帮手,用来快速做出一份普通的函数工具调用。它让测试不用每次手写一大堆 ToolInvocation 字段。
数据流:进去的是会话、当前轮次、调用编号、工具名、调用来源和字符串参数 → 函数把工具名转成标准 ToolName,把参数包装成 Function 类型的 ToolPayload → 再交给 test_invocation_with_payload 生成完整调用对象。
调用关系:多个测试都会用它来制造正常形态的工具调用。它自己不直接拼完整结构,而是把最后组装工作交给 test_invocation_with_payload,这样普通调用和特殊 payload 调用能共用同一套创建逻辑。
调用图:调用 2 个内部函数(test_invocation_with_payload, plain);被 3 处调用(dispatch_lifecycle_trace_records_direct_and_code_mode_requesters, dispatch_lifecycle_trace_records_unsupported_tool_failures, missing_code_mode_wait_traces_only_the_wait_tool_call)。
test_invocation_with_payload264–282 ↗
fn test_invocation_with_payload(
session: Arc<Session>,
turn: Arc<TurnContext>,
call_id: &str,
tool_name: codex_tools::ToolName,
source: ToolCallSource,
payload: ToolPayload,
)
作用:这是更底层的测试帮手,用来生成完整的 ToolInvocation。它允许测试自己指定 payload,所以能制造正常输入,也能制造故意错误的输入。
数据流:进去的是会话、轮次、调用编号、工具名、来源和 payload → 函数补上取消令牌、差异追踪器等调度需要的配件 → 出来的是一份完整 ToolInvocation,可直接交给工具注册表分发。
调用关系:test_invocation 会调用它来创建普通函数调用;不兼容 payload 的测试会直接调用它来绕过默认包装。它生成的对象随后交给 ToolRegistry::dispatch_any,进入真实的工具调度流程。
调用图:调用 1 个内部函数(new);被 2 处调用(dispatch_lifecycle_trace_records_incompatible_payload_failures, test_invocation);外部调用 3 个(new, new, new)。
attach_test_trace284–307 ↗
fn attach_test_trace(session: &mut Session, turn: &TurnContext, root: &Path) -> anyhow::Result<()>
作用:给测试会话接上一套临时追踪系统。没有它,工具调用虽然会执行,但测试就没有追踪文件可以回放和检查。
数据流:进去的是可修改的 Session、当前 TurnContext 和一个临时根目录 → 函数用线程编号、工作目录、模型名、沙箱策略等测试元数据启动一条追踪,并记录本轮对话开始 → 最后把这个追踪上下文塞回 session.services.rollout_thread_trace。
调用关系:所有追踪相关测试开始前都会调用它。它负责把普通测试会话变成“会写追踪文件”的会话,之后 ToolRegistry 分发工具时产生的记录才会落到临时目录里。
调用图:调用 1 个内部函数(start_root_in_root_for_test);被 4 处调用(dispatch_lifecycle_trace_records_direct_and_code_mode_requesters, dispatch_lifecycle_trace_records_incompatible_payload_failures, dispatch_lifecycle_trace_records_unsupported_tool_failures, missing_code_mode_wait_traces_only_the_wait_tool_call);外部调用 1 个(from)。
single_bundle_dir309–316 ↗
fn single_bundle_dir(root: &Path) -> anyhow::Result<PathBuf>
作用:从临时追踪根目录里找出唯一的追踪包目录。测试用它来定位刚刚写出的记录,再拿去回放检查。
数据流:进去的是追踪根目录路径 → 函数读取这个目录下的所有条目,排序,并断言里面刚好只有一个 → 出来的是那一个追踪包目录的路径;如果数量不对,测试会直接失败。
调用关系:每个测试在工具调用结束后都会调用它。它把“临时目录里到底生成了哪个追踪包”这个小麻烦解决掉,然后结果交给 codex_rollout_trace::replay_bundle 去读回追踪内容。
调用图:被 4 处调用(dispatch_lifecycle_trace_records_direct_and_code_mode_requesters, dispatch_lifecycle_trace_records_incompatible_payload_failures, dispatch_lifecycle_trace_records_unsupported_tool_failures, missing_code_mode_wait_traces_only_the_wait_tool_call);外部调用 2 个(assert_eq!, read_dir)。
审批、沙盒和运行时准备
这些文件涵盖审批规范化、沙盒策略辅助工具,以及安全准备命令和补丁执行的运行时设置路径。
core/src/apply_patch_tests.rs源码 ↗
这个测试像是在检查一张快递单有没有抄对:内部代码里有一种“我要新增这个文件,并写入这些内容”的动作,真正发给外部协议时,也必须还是“新增这个文件,内容是这些”。测试先建一个临时目录,避免碰到真实文件;再拼出一个测试用的文件路径;然后用 ApplyPatchAction::new_add_for_test 做出一个“添加 a.txt,内容是 hello”的动作。接着调用 convert_apply_patch_to_protocol,把内部动作转换成协议格式。最后用断言检查:转换结果里,这个路径对应的变化确实是 FileChange::Add,而且内容正好是 hello。这个文件不参与正式运行,只在跑测试时保护这段转换规则不被破坏。
convert_apply_patch_maps_add_variant8–22 ↗
fn convert_apply_patch_maps_add_variant()
作用:这个测试函数确认:当内部补丁动作表示“新增文件”时,转换后的协议数据也会明确表示“新增文件”,并保留原来的文件内容。有人改补丁转换代码后,可以靠它立刻发现新增文件这条路有没有被改坏。
数据流:进去的是测试临时目录生成的一个文件路径,以及字符串内容“hello”。它先用 tempdir(ext) 创建临时目录,再用 new_add_for_test 做出一个测试用的新增文件动作;然后把这个动作交给 convert_apply_patch_to_protocol 转换;最后用 assert_eq!(ext) 比较结果:这个路径对应的输出必须是 FileChange::Add,里面的 content 必须还是“hello”。它不改真实项目文件,只创建和使用临时测试数据。
调用关系:它是测试框架在跑单元测试时调用的函数。它自己负责搭好一个最小场景:先请 tempdir(ext) 准备安全的临时位置,再请 new_add_for_test 造出内部补丁动作,随后调用被测试的转换函数,最后用 assert_eq!(ext) 判断转换结果是否符合预期。
调用图:调用 1 个内部函数(new_add_for_test);外部调用 2 个(assert_eq!, tempdir)。
core/src/command_canonicalization_tests.rs源码 ↗
这是一组自动测试。它检查 canonicalize_command_for_approval 这个函数是否能把用户要执行的命令整理成稳定、可比较的样子。举个例子,/bin/bash -lc "cargo test" 和 bash -lc "cargo test" 看起来不一样,但真正要跑的都是 cargo test,所以审批时应该当成同一个东西。这个文件还测试了更复杂的脚本,比如 heredoc(一种把多行文本塞给命令的写法)和 PowerShell 包装命令。对于普通命令,比如 cargo fmt,它又要求函数不要乱改。简单说,它像门卫的校验题库:确保门卫看的是“真正要进门的人”,而不是被外套、帽子或空格迷惑。
canonicalizes_word_only_shell_scripts_to_inner_command5–30 ↗
fn canonicalizes_word_only_shell_scripts_to_inner_command()
作用:这个测试确认:如果 shell 里只是包了一条普通命令,系统应该提取出里面真正的命令。这样 /bin/bash 和 bash、多个空格和单个空格,不会导致审批结果不一致。
数据流:进去的是两组字符串列表,分别表示两种写法的 shell 命令:一个用 /bin/bash,一个用 bash,里面都是 cargo test。测试把它们交给 canonicalize_command_for_approval 整理,然后用 assert_eq! 对比结果。出来的结果应该都是同一个列表:cargo、test、-p、codex-core;测试本身不改系统状态,只判断结果对不对。
调用关系:它是测试套件里验证“普通 shell 包装命令”的用例。它用 vec! 造出测试数据,再用 assert_eq! 检查整理后的结果是否符合预期;如果命令规范化函数把空格或 shell 路径当成差异,这个测试就会失败。
调用图:外部调用 2 个(assert_eq!, vec!)。
canonicalizes_heredoc_scripts_to_stable_script_key33–54 ↗
fn canonicalizes_heredoc_scripts_to_stable_script_key()
作用:这个测试确认:遇到 heredoc 这种多行脚本时,系统不要硬拆里面的命令,而是用一个稳定的脚本标记来代表它。这样同一段脚本不管用 /bin/zsh 还是 zsh 包起来,审批时都能认成同一个。
数据流:进去的是一段 Python heredoc 脚本,以及两组 zsh 包装方式。测试把这些命令列表交给 canonicalize_command_for_approval。它期望出来的是一个以 __codex_shell_script__ 开头的列表,后面保留 -lc 和原始脚本文本;同时两种 zsh 写法整理后必须相同。
调用关系:它覆盖的是“复杂 shell 脚本”的情况。它用 vec! 准备命令,用 assert_eq! 检查规范化结果;它和普通命令测试互补,说明系统什么时候应该拆出内部命令,什么时候应该把整段脚本当成一个稳定对象。
调用图:外部调用 2 个(assert_eq!, vec!)。
canonicalizes_powershell_wrappers_to_stable_script_key57–82 ↗
fn canonicalizes_powershell_wrappers_to_stable_script_key()
作用:这个测试确认:PowerShell 包装的脚本会被整理成稳定格式,而不是因为 powershell.exe、powershell 或参数差异就被看成不同命令。这样 Windows 环境下的审批判断更可靠。
数据流:进去的是一段 PowerShell 脚本 Write-Host hi,以及两种 PowerShell 调用方式。测试把命令列表交给 canonicalize_command_for_approval,期望出来的是 __codex_powershell_script__ 加脚本文本。它还检查两种包装方式整理后完全一样。
调用关系:它负责测试 PowerShell 这条分支。它通过 vec! 创建输入,通过 assert_eq! 验证输出;如果规范化函数没有正确忽略 -NoProfile 这类包装参数,或者没有把 PowerShell 脚本归成稳定标记,这个测试会指出问题。
调用图:外部调用 2 个(assert_eq!, vec!)。
preserves_non_shell_commands85–88 ↗
fn preserves_non_shell_commands()
作用:这个测试确认:如果命令本来就不是 shell 包装脚本,系统不应该自作聪明去改它。比如 cargo fmt 就应该原样保留。
数据流:进去的是一个普通命令列表:cargo 和 fmt。测试把它交给 canonicalize_command_for_approval,然后用 assert_eq! 确认出来的结果和原输入完全一样。也就是说,之前是什么普通命令,之后还是什么普通命令。
调用关系:它是防止“过度整理”的保护用例。前面的测试确认该改的要改,这个测试确认不该改的别改;它同样用 vec! 构造输入,用 assert_eq! 做最终判断。
调用图:外部调用 2 个(assert_eq!, vec!)。
core/src/tasks/user_shell_tests.rs源码 ↗
这个测试文件专门检查一个细节:当系统准备在用户的 shell(比如 Bash)里执行命令时,会同时处理两件事。一件是读取 shell 快照文件,里面可能保存了用户原来的环境变量,比如 PATH。另一件是把程序运行时需要的目录放到 PATH 最前面。这里的风险是,快照文件一加载,就可能把前面刚加的目录冲掉。测试用临时目录造了一个假的快照文件,里面写着 PATH 是“/snapshot/bin”;再模拟运行时要把一个“codex-path”目录加到最前面。最后它真的启动 Bash,打印 PATH,并检查结果必须是“codex-path:/snapshot/bin”。这就像排队买票:程序自己的工具目录必须站在队伍最前面,但后面仍然保留用户快照里的队伍顺序。
shell_with_snapshot9–21 ↗
fn shell_with_snapshot(
shell_type: ShellType,
shell_path: &str,
snapshot_path: AbsolutePathBuf,
) -> (Shell, AbsolutePathBuf)
作用:这个小帮手用来快速拼出一个“shell 信息”和“快照文件路径”的组合,方便测试代码少写重复内容。有人会用它,是因为测试只关心 shell 类型、shell 路径和快照位置,不想每次手动组装结构。
数据流:进去的是 shell 类型、shell 程序路径字符串、快照文件的绝对路径;它把 shell 类型和路径装进一个 Shell 对象里,并把快照路径原样带上;出来的是一对值:准备好的 Shell,以及对应的快照文件路径。它不读文件,也不改外部状态。
调用关系:它被 user_shell_snapshot_preserves_package_path_prepend 调用,作为测试里的准备步骤。它自己只做组装,不负责真正改写命令;后面的测试会把它产出的 shell 信息交给被测的命令准备函数。
调用图:被 1 处调用(user_shell_snapshot_preserves_package_path_prepend);外部调用 1 个(from)。
user_shell_snapshot_preserves_package_path_prepend24–62 ↗
fn user_shell_snapshot_preserves_package_path_prepend()
作用:这个测试确认:即使加载了用户 shell 的快照文件,运行时临时加到 PATH 最前面的目录仍然会保留在最前面。这样可以保证程序优先找到自己需要的工具,而不是误用用户环境里的同名命令。
数据流:一开始它创建一个临时目录,并写入一个假的 snapshot.sh,里面把 PATH 设成“/snapshot/bin”;然后它准备一个 Bash 命令,让 Bash 打印当前 PATH;接着它设置初始 PATH,并调用命令改写函数,在过程中把测试用的 codex-path 目录加到 PATH 前面;之后它真的运行改写后的命令,读取输出;最后它断言命令成功,并且输出正好是“codex-path:/snapshot/bin”。这个测试会创建临时文件、启动一个子进程,并检查子进程输出。
调用关系:这是整个文件的核心测试用例,由测试框架在运行测试时自动调用。它先用 shell_with_snapshot 准备 shell 和快照信息,再把这些信息交给 prepare_user_shell_exec_command_with_path_prepend 这类真正被测的代码,最后用系统的 Command 启动改写后的命令,并用断言确认行为正确。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(from, new, assert!, assert_eq!, new, write, tempdir, vec!)。
core/src/tools/network_approval_tests.rs源码 ↗
这是一组自动化测试。可以把网络审批想成小区门禁:同一个地址、同一种协议、同一个端口的访问请求,只应该排一次队等业主决定;不同端口则像不同门,不能混在一起。文件先检查待审批请求会不会正确合并,再检查本次会话已经批准的主机是否会按“主机 + 协议 + 端口”完整保存。后半部分更关注工具调用:当一个 shell 命令因为联网被沙箱拦下时,服务要找到是哪次工具调用触发的,取消它,并记录是策略拒绝还是用户拒绝。这里还测试了几个容易出错的边界:用户拒绝不能被后来的策略拒绝覆盖;已经结束的调用不能再被改结果;如果同时有多个活跃调用,系统不能瞎猜是谁触发了网络请求。
pending_approvals_are_deduped_per_host_protocol_and_port13–27 ↗
async fn pending_approvals_are_deduped_per_host_protocol_and_port()
作用:测试同一个主机、协议和端口的网络审批请求会被合并。也就是说,两个人问同一扇门能不能开,系统只让第一个人负责去问用户,后面的人等同一个结果。
数据流:它创建一个空的 NetworkApprovalService,然后用同一个 HostApprovalKey 连续申请两次待审批项。第一次应该拿到“我是负责人”的标记,第二次应该不是负责人,而且两次拿到的是同一个待审批对象。
调用关系:这是直接检查 NetworkApprovalService 的排队去重行为。它只调用默认构造和断言,不把工作交给本文件里的其他辅助函数。
pending_approvals_do_not_dedupe_across_ports30–49 ↗
session_approved_hosts_preserve_protocol_and_port_scope52–107 ↗
async fn session_approved_hosts_preserve_protocol_and_port_scope()
作用:测试“本次会话已批准的网络地址”在复制到另一个服务时,不会丢掉协议和端口这些范围信息。
数据流:它先往源服务里塞入三个已批准地址:同一主机但协议或端口不同。然后把这些批准同步到另一个服务,再取出来排序并比较,确认三条都原样存在。
调用关系:它验证 sync_session_approved_hosts_to 这类同步行为的正确性。这个测试不依赖辅助函数,直接操作服务内部保存的会话批准集合。
调用图:调用 1 个内部函数(default);外部调用 1 个(assert_eq!)。
sync_session_approved_hosts_to_replaces_existing_target_hosts110–149 ↗
async fn sync_session_approved_hosts_to_replaces_existing_target_hosts()
作用:测试同步已批准主机时,目标服务里旧的批准记录会被源服务的新记录替换掉,而不是混在一起。
数据流:它先给源服务放入 source.example.com,再给目标服务放入 stale.example.com。同步之后,目标服务里应该只剩源服务那条记录,旧记录被清掉。
调用关系:它专门检查同步动作的覆盖语义,防止旧批准残留造成错误放行。它直接使用服务和断言。
调用图:调用 1 个内部函数(default);外部调用 1 个(assert_eq!)。
pending_waiters_receive_owner_decision152–166 ↗
async fn pending_waiters_receive_owner_decision()
作用:测试等待审批结果的人,能收到负责审批的人最后做出的决定。
数据流:它新建一个 PendingHostApproval,把一个异步任务挂在 wait_for_decision 上等待。随后设置决定为 AllowOnce,等待任务结束后应该拿到同样的决定。
调用关系:它验证 PendingHostApproval 像公告板一样工作:一个地方写入决定,其他等待者都能读到。它用 tokio::spawn 启动等待任务。
调用图:调用 1 个内部函数(new);外部调用 4 个(clone, new, assert_eq!, spawn)。
allow_once_and_allow_for_session_both_allow_network169–178 ↗
fn allow_once_and_allow_for_session_both_allow_network()
作用:测试“只允许这一次”和“本次会话都允许”最终都会转成网络层的放行决定。
数据流:它分别把 AllowOnce 和 AllowForSession 转成 NetworkDecision,然后检查结果都是 Allow。
调用关系:它检查审批决定到网络执行决定之间的转换规则。这个规则会影响后续代理或沙箱到底放不放网络请求。
调用图:外部调用 1 个(assert_eq!)。
only_never_policy_disables_network_approval_flow181–186 ↗
fn only_never_policy_disables_network_approval_flow()
作用:测试只有 AskForApproval::Never 会关闭网络审批流程。其他模式仍然允许系统在需要时询问用户。
数据流:它把 Never、OnRequest、OnFailure、UnlessTrusted 这些审批策略逐个传入判断函数。结果应该是 Never 返回否,其他返回是。
调用关系:它保护审批开关的高层规则,避免把本该能询问用户的模式误关掉。
调用图:外部调用 1 个(assert!)。
network_approval_flow_is_limited_to_restricted_sandbox_modes189–204 ↗
fn network_approval_flow_is_limited_to_restricted_sandbox_modes()
作用:测试网络审批只在受限制的沙箱模式下启用。沙箱可以理解成给程序活动范围画的一圈围栏。
数据流:它检查只读模式和工作区可写模式允许网络审批;完全禁用沙箱或外部自定义沙箱则不走这套审批流程。
调用关系:它验证权限配置和网络审批之间的边界。这样可以避免在不该由本系统控制的模式下弹出审批。
调用图:外部调用 1 个(assert!)。
denied_blocked_request206–218 ↗
fn denied_blocked_request(host: &str) -> BlockedRequest
作用:这是测试用的小工具函数,用来快速造一个“网络请求被拒绝”的记录。测试里不用每次都手写一大堆字段。
数据流:输入一个主机名,它把主机名、拒绝原因、http 协议、80 端口、拒绝决定等信息装进 BlockedRequestArgs,再生成 BlockedRequest 返回。
调用关系:它被多个和被拦截网络请求有关的测试调用,比如策略拒绝、用户拒绝不被覆盖、多个调用时不瞎归因等场景。
调用图:调用 1 个内部函数(new);被 3 处调用(blocked_request_policy_does_not_override_user_denial_outcome, record_blocked_request_ignores_ambiguous_unattributed_blocked_requests, record_blocked_request_sets_policy_outcome_for_owner_call)。
register_call_with_default_shell_trigger220–244 ↗
async fn register_call_with_default_shell_trigger(
service: &NetworkApprovalService,
registration_id: &str,
) -> CancellationToken
作用:这是测试用的登记工具调用函数。它模拟一次 shell 命令触发网络访问,比如运行 curl 去访问 example.com。
数据流:输入服务和登记编号。它创建一个 CancellationToken,也就是之后可以用来取消这次调用的信号;然后把调用编号、轮次、命令、工作目录、沙箱权限等信息登记进 NetworkApprovalService,最后返回这个取消信号。
调用关系:许多测试先用它搭好一个活跃调用,再测试网络被拦时服务怎么记录结果、取消调用或结束调用。它把准备工作集中起来,避免每个测试重复写同样的登记代码。
调用图:调用 1 个内部函数(register_call);被 6 处调用(blocked_request_policy_does_not_override_user_denial_outcome, deferred_finish_reuses_denial_result_after_first_consumer, finish_call_returns_denial_and_unregisters_active_call, record_blocked_request_ignores_ambiguous_unattributed_blocked_requests, record_blocked_request_sets_policy_outcome_for_owner_call, record_call_outcome_ignores_inactive_call);外部调用 3 个(new, test_path_buf, vec!)。
active_call_preserves_triggering_command_context247–277 ↗
async fn active_call_preserves_triggering_command_context()
作用:测试服务登记活跃调用时,会完整保留触发网络访问的命令上下文。这样审批界面或错误信息才能告诉用户到底是哪条命令想联网。
数据流:它创建一个带 call_id、工具名、命令、工作目录、理由等信息的触发器,登记进服务。随后解析唯一的活跃调用,检查取出的触发器和命令字符串都和原来一致。
调用关系:它直接测试 register_call 和 resolve_single_active_call 配合后的效果,确保后续审批或拒绝时不会丢失上下文。
调用图:调用 1 个内部函数(default);外部调用 4 个(new, assert_eq!, test_path_buf, vec!)。
record_blocked_request_sets_policy_outcome_for_owner_call280–296 ↗
async fn record_blocked_request_sets_policy_outcome_for_owner_call()
作用:测试当某个活跃调用触发的网络请求被策略拦下时,服务会取消这个调用,并记录“被策略拒绝”的结果。
数据流:它先登记一个默认 shell 调用并拿到取消信号,再记录一个针对 example.com 的被拒网络请求。之后取消信号应该变成已取消,取出的调用结果应该是带说明文字的 DeniedByPolicy。
调用关系:它使用 register_call_with_default_shell_trigger 搭建调用,用 denied_blocked_request 制造拦截事件,然后检查 NetworkApprovalService 是否把拦截事件正确归到这次调用。
调用图:调用 3 个内部函数(default, denied_blocked_request, register_call_with_default_shell_trigger);外部调用 2 个(assert!, assert_eq!)。
blocked_request_policy_does_not_override_user_denial_outcome299–314 ↗
async fn blocked_request_policy_does_not_override_user_denial_outcome()
作用:测试如果用户已经拒绝了某次调用,后来的策略拒绝不能把这个结果覆盖掉。用户的决定优先保留。
数据流:它先登记调用,再手动记录结果为 DeniedByUser。随后又记录一个策略拦截的网络请求。最后取结果时,仍然应该是 DeniedByUser。
调用关系:它使用登记辅助函数和拒绝请求辅助函数,检查 record_call_outcome 与 record_blocked_request 的先后关系。这个测试防止错误原因被改写。
调用图:调用 3 个内部函数(default, denied_blocked_request, register_call_with_default_shell_trigger);外部调用 1 个(assert_eq!)。
finish_call_returns_denial_and_unregisters_active_call317–336 ↗
async fn finish_call_returns_denial_and_unregisters_active_call()
作用:测试结束一次调用时,如果之前已经记录了拒绝结果,finish_call 会把拒绝作为错误返回,并且把这个调用从活跃列表里移除。
数据流:它登记调用,记录一个 DeniedByPolicy 结果,然后调用 finish_call。返回值应该是 ToolError::Rejected,活跃调用应该查不到了,结果也应该被取走清空。
调用关系:它验证调用生命周期的收尾阶段:登记、记录拒绝、结束、清理。它依赖 register_call_with_default_shell_trigger 创建初始调用。
调用图:调用 2 个内部函数(default, register_call_with_default_shell_trigger);外部调用 3 个(assert!, assert_eq!, DeniedByPolicy)。
deferred_finish_reuses_denial_result_after_first_consumer339–366 ↗
async fn deferred_finish_reuses_denial_result_after_first_consumer()
作用:测试延迟结束对象即使被调用两次,也会复用第一次拿到的拒绝结果,而不是第二次因为结果已被取走就变成不一致。
数据流:它登记调用,创建 DeferredNetworkApproval,并记录策略拒绝。第一次调用 finish 得到拒绝错误;第二次再调用 finish,也应该得到同样的拒绝错误。
调用关系:它测试 DeferredNetworkApproval 和 NetworkApprovalService 的配合,重点是 OnceCell 这种“只保存一次结果的盒子”能缓存结束结果。
调用图:调用 2 个内部函数(default, register_call_with_default_shell_trigger);外部调用 4 个(new, new, assert!, DeniedByPolicy)。
record_call_outcome_ignores_inactive_call369–384 ↗
async fn record_call_outcome_ignores_inactive_call()
作用:测试已经注销的调用不会再被记录结果,也不会被取消。这样旧事件不会误伤已经结束的任务。
数据流:它先登记一个调用并拿到取消信号,然后立刻注销这个调用。之后再尝试记录策略拒绝,取消信号应该没有被触发,服务里也取不到任何结果。
调用关系:它验证 unregister_call 之后 record_call_outcome 的防护行为。它使用登记辅助函数创建可观察的取消信号。
调用图:调用 2 个内部函数(default, register_call_with_default_shell_trigger);外部调用 3 个(assert!, assert_eq!, DeniedByPolicy)。
record_blocked_request_ignores_ambiguous_unattributed_blocked_requests387–398 ↗
async fn record_blocked_request_ignores_ambiguous_unattributed_blocked_requests()
作用:测试当同时有多个活跃调用,而一个被拦截的网络请求没有明确归属时,服务不会随便挑一个调用背锅。
数据流:它登记两个活跃调用,然后记录一个没有明确指向哪个调用的被拒网络请求。最后两个登记编号都不应该有拒绝结果。
调用关系:它使用 register_call_with_default_shell_trigger 创建“场面混乱”的情况,再用 denied_blocked_request 制造拦截事件,验证服务在无法确定归属时选择安全地不处理。
调用图:调用 3 个内部函数(default, denied_blocked_request, register_call_with_default_shell_trigger);外部调用 1 个(assert_eq!)。
core/src/tools/sandboxing_tests.rs源码 ↗
这份文件不负责真正执行命令,而是像安全规则的“考卷”。它用一个个小测试确认:生成权限申请内容时,空的说明不要乱塞进去;有说明时要带上;不同沙箱策略下,命令执行是直接放行、需要用户批准,还是被禁止。这里的“沙箱”可以理解成给程序活动范围画的一圈围栏,防止它随便读写文件;“审批”就是执行危险操作前先问用户。文件还特别测试了一个容易出错的情况:如果规则里明确禁止读取某些文件,比如 .env 文件,那么即使用户或策略看起来允许“升级权限”,第一次执行也不能直接拆掉沙箱。整体上,它是在保护安全边界,防止未来改代码时不小心把这些细节弄坏。
bash_permission_request_payload_omits_missing_description12–20 ↗
fn bash_permission_request_payload_omits_missing_description()
作用:这个测试确认:给 bash 命令生成权限申请内容时,如果没有额外说明,就只放命令本身。这样申请内容不会多出空字段,避免让后续读取的人或程序误解。
数据流:进去的是一条命令“echo hi”和一个空的说明 → 测试调用生成权限申请内容的函数 → 出来的结果应当只有工具名和 command 字段;测试用 assert_eq!(比较左右是否完全一样)确认它没有偷偷加入 description。
调用关系:它站在权限申请格式的入口处做检查,模拟上层准备向用户或系统申请执行 bash 命令。它只把结果交给 assert_eq! 做对比,不再调用别的项目逻辑。
调用图:外部调用 1 个(assert_eq!)。
bash_permission_request_payload_includes_description_when_present23–37 ↗
fn bash_permission_request_payload_includes_description_when_present()
作用:这个测试确认:如果 bash 命令带了说明文字,权限申请内容里必须包含这段说明。这样用户看到申请时,能知道这条命令为什么需要额外能力。
数据流:进去的是命令“echo hi”和说明“network-access example.com” → 生成权限申请内容 → 出来的 JSON 数据里应同时有 command 和 description;测试用 assert_eq! 检查实际结果和预期完全一致。
调用关系:它补上了前一个测试的另一半情况:没有说明时不写,有说明时必须写。它模拟的是工具准备发起权限请求的阶段,最后由 assert_eq! 判断格式是否正确。
调用图:外部调用 1 个(assert_eq!)。
external_sandbox_skips_exec_approval_on_request40–51 ↗
fn external_sandbox_skips_exec_approval_on_request()
作用:这个测试确认:当使用“外部沙箱”时,在“按需询问批准”的策略下,默认不需要再额外申请执行批准。意思是外部系统已经负责看守,不必重复拦一次。
数据流:进去的是“OnRequest”这种审批策略,以及一个外部沙箱策略 → 测试调用默认执行审批判断 → 出来的结果应是 Skip,也就是跳过审批,但不绕过沙箱;测试用 assert_eq! 验证这一点。
调用关系:它检查默认审批判断函数在外部沙箱场景下的分支。这个测试不自己做安全判断,而是把策略交给被测函数,再用 assert_eq! 确认结论。
调用图:外部调用 1 个(assert_eq!)。
restricted_sandbox_requires_exec_approval_on_request54–65 ↗
fn restricted_sandbox_requires_exec_approval_on_request()
作用:这个测试确认:在普通受限沙箱里,如果策略是“需要时询问”,执行命令默认应该先要用户批准。这样危险命令不会在受限环境下悄悄运行。
数据流:进去的是“OnRequest”审批策略和默认文件系统沙箱策略 → 默认审批判断函数根据这些信息做决定 → 出来的结果应是 NeedsApproval,也就是需要批准;测试用 assert_eq! 检查结果。
调用关系:它和外部沙箱测试形成对照:外部沙箱可跳过,受限沙箱要询问。它调用默认策略构造方法拿到常见沙箱设置,再验证核心判断函数。
调用图:外部调用 1 个(assert_eq!)。
default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disables_it68–86 ↗
fn default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disables_it()
作用:这个测试确认:如果细粒度审批配置明确关掉了“沙箱审批提示”,系统不能再弹出这类提示,而应该直接判定为禁止。细粒度审批就是把不同种类的批准开关分开控制。
数据流:进去的是一个 Granular 配置,其中 sandbox_approval 被设为 false,以及默认沙箱策略 → 默认执行审批判断看到这个开关被关掉 → 出来的结果应是 Forbidden,并带有“审批策略不允许沙箱审批提示”的原因;测试用 assert_eq! 核对。
调用关系:它覆盖的是审批策略里更细的开关规则。测试先构造 Granular 配置,再调用默认审批判断函数,最后确认被测函数没有绕过这个关闭状态。
调用图:调用 1 个内部函数(default);外部调用 2 个(Granular, assert_eq!)。
default_exec_approval_requirement_keeps_prompt_when_granular_allows_sandbox_approval89–108 ↗
fn default_exec_approval_requirement_keeps_prompt_when_granular_allows_sandbox_approval()
作用:这个测试确认:如果细粒度审批配置允许“沙箱审批提示”,系统就应该保留正常的询问流程。也就是说,开关打开时不能误判成禁止或跳过。
数据流:进去的是一个 Granular 配置,其中 sandbox_approval 为 true,以及默认沙箱策略 → 默认执行审批判断按允许提示来处理 → 出来的结果应是 NeedsApproval,没有额外原因,也没有提出修改执行策略;测试用 assert_eq! 检查。
调用关系:它和前一个测试是一对正反用例:一个验证关掉会禁止,一个验证打开会继续询问。它同样通过 Granular 配置进入被测判断函数。
调用图:调用 1 个内部函数(default);外部调用 2 个(Granular, assert_eq!)。
additional_permissions_allow_bypass_sandbox_first_attempt_when_execpolicy_skips111–123 ↗
fn additional_permissions_allow_bypass_sandbox_first_attempt_when_execpolicy_skips()
作用:这个测试确认:当命令已经被执行策略允许跳过审批,并且还请求了额外权限时,第一次尝试可以直接绕过沙箱。这里是在验证“策略明确放行”时,沙箱覆盖规则是否跟着放行。
数据流:进去的是“带额外权限”的沙箱权限、一个 Skip 且 bypass_sandbox 为 true 的执行审批结果,以及默认沙箱策略 → 覆盖规则判断第一次执行该怎么跑 → 出来的结果应是 BypassSandboxFirstAttempt,也就是第一次就不进沙箱;测试用 assert_eq! 验证。
调用关系:它测试的是执行审批结果和沙箱权限之间的配合。上一步审批说可以绕过沙箱,这里确认第一次运行的沙箱覆盖逻辑会尊重这个决定。
调用图:外部调用 1 个(assert_eq!)。
guardian_bypasses_sandbox_for_explicit_escalation_on_first_attempt126–138 ↗
fn guardian_bypasses_sandbox_for_explicit_escalation_on_first_attempt()
作用:这个测试确认:当权限要求明确是“需要升级权限”时,即使执行审批结果本身没有写明绕过沙箱,第一次尝试也会绕过沙箱。这是在检查显式升级请求是否真的生效。
数据流:进去的是 RequireEscalated 这种明确升级权限的请求、一个 Skip 但 bypass_sandbox 为 false 的审批结果,以及默认沙箱策略 → 第一次执行的沙箱覆盖判断读取这些条件 → 出来的结果应是 BypassSandboxFirstAttempt;测试用 assert_eq! 检查。
调用关系:它关注的是用户或守护逻辑明确要求升级权限的场景。它把这个请求交给沙箱覆盖判断函数,确认该函数会把第一次执行切到非沙箱模式。
调用图:外部调用 1 个(assert_eq!)。
deny_read_blocks_explicit_escalation_and_policy_bypass141–195 ↗
fn deny_read_blocks_explicit_escalation_and_policy_bypass()
作用:这个测试确认一个关键安全底线:如果文件系统策略里有“禁止读取”的规则,比如禁止读所有 .env 文件,那么显式升级权限或策略放行都不能让第一次执行绕过沙箱。因为绕过沙箱会把这条禁止读取规则一起丢掉。
数据流:进去的是一个受限文件系统策略,里面用 glob 模式(按通配符匹配路径)禁止访问 **/*.env → 测试分别检查升级权限、允许绕过沙箱的执行策略、保留禁止读取的权限调整等情况 → 出来的结果应始终保护 deny-read 规则:不能无沙箱执行,必要时把 RequireEscalated 降回 UseDefault,但 WithAdditionalPermissions 仍可保留;多个 assert_eq! 和 assert! 逐项确认这些结果。
调用关系:它是这组测试里最像安全回归测试的一项,专门防止未来改动 accidentally 拆掉“禁止读取”保护。它会调用 restricted 构造受限策略,再检查 sandbox_override_for_first_attempt、unsandboxed_execution_allowed 和 sandbox_permissions_preserving_denied_reads 这些判断是否互相配合,守住同一条安全边界。
调用图:调用 1 个内部函数(restricted);外部调用 3 个(assert!, assert_eq!, vec!)。
core/src/tools/runtimes/apply_patch_tests.rs源码 ↗
“apply_patch”可以理解成一个自动改文件的工具。它很有用,但也危险:如果权限、目录、环境编号或沙箱设置传错了,程序可能改到不该改的文件,或者审批人看不到真实要改什么。这个测试文件就是给这套流程做安全体检。它先造出假的运行环境和假的补丁请求,然后检查几个重点:该不该请求“无沙箱审批”、给守护审批系统的请求里是否带上了补丁内容和文件路径、权限请求是否用了正确的工具名和别名、审批缓存的 key 是否包含环境编号、沙箱工作目录是否用补丁动作自己的目录。最后还测试两种沙箱情况:有真实沙箱尝试时,要把额外文件权限合并进去;如果根本不启用沙箱,就不应该生成文件系统沙箱上下文。
test_turn_environment18–25 ↗
fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::TurnEnvironment
作用:这个小帮手用来快速造一个测试用的“回合环境”。回合环境可以理解成一次对话或一次工具调用所在的执行背景,包括环境编号、临时目录等。
数据流:输入一个环境编号字符串 → 它创建一个默认的测试执行环境,把系统临时目录转成项目内部使用的路径格式,并且不指定 shell → 输出一个可以塞进补丁请求里的 TurnEnvironment。
调用关系:多个测试都会先调用它来准备假环境,然后再去测试审批 key、权限请求、守护审批请求、沙箱目录和沙箱上下文。它本身不验证业务结果,只负责把测试舞台搭好。
调用图:调用 3 个内部函数(new, default_for_tests, from_abs_path);被 6 处调用(approval_keys_include_environment_id, file_system_sandbox_context_uses_active_attempt, guardian_review_request_includes_patch_context, no_sandbox_attempt_has_no_file_system_context, permission_request_payload_uses_apply_patch_hook_name_and_aliases, sandbox_cwd_uses_patch_action_cwd);外部调用 2 个(temp_dir, new)。
wants_no_sandbox_approval_granular_respects_sandbox_flag28–49 ↗
fn wants_no_sandbox_approval_granular_respects_sandbox_flag()
作用:这个测试确认:当审批配置很细时,是否需要“无沙箱审批”只看 sandbox_approval 这个开关。这样可以避免配置里其他审批开关误影响沙箱安全判断。
数据流:它创建 ApplyPatchRuntime → 分别喂入普通按需审批、细粒度审批但沙箱审批关闭、细粒度审批且沙箱审批开启 → 检查返回值是否分别符合预期。
调用关系:它直接测试 ApplyPatchRuntime 的 wants_no_sandbox_approval 判断逻辑,不把活儿交给别的测试辅助函数。这个测试守住的是审批策略入口处的一条安全规则。
guardian_review_request_includes_patch_context52–88 ↗
async fn guardian_review_request_includes_patch_context()
作用:这个测试确认交给守护审批系统的请求里,真的包含补丁内容、当前目录和涉及的文件。守护审批系统可以理解成“安全审核员”,它必须看到完整材料才能判断能不能放行。
数据流:它先在临时目录造一个新增文件的补丁动作 → 包成 ApplyPatchRequest,并标记需要审批 → 调用 build_guardian_review_request → 得到 GuardianApprovalRequest → 检查里面的 id、工作目录、文件列表、补丁文本都和原请求一致。
调用关系:它使用 test_turn_environment 搭测试环境,再调用 ApplyPatchRuntime::build_guardian_review_request。这个测试覆盖的是补丁请求进入外部审核前,信息是否被完整转交。
调用图:调用 3 个内部函数(new_add_for_test, build_guardian_review_request, test_turn_environment);外部调用 4 个(from, assert_eq!, temp_dir, vec!)。
permission_request_payload_uses_apply_patch_hook_name_and_aliases91–124 ↗
async fn permission_request_payload_uses_apply_patch_hook_name_and_aliases()
作用:这个测试确认权限请求对外报出的工具名是 apply_patch,并且带有 Write、Edit 这些别名。这样权限规则既能按真实工具名匹配,也能按“写文件/编辑文件”这类通用动作匹配。
数据流:它创建运行器和一个新增文件补丁请求 → 调用 permission_request_payload → 得到一份权限申请载荷 → 检查工具名、匹配别名,以及传给审批方的输入内容是否就是补丁命令。
调用关系:它先用 test_turn_environment 和 new_add_for_test 准备请求,再让 ApplyPatchRuntime 生成权限申请。它测试的是“补丁工具向权限系统自报身份”这一步是否准确。
调用图:调用 3 个内部函数(new_add_for_test, new, test_turn_environment);外部调用 4 个(new, assert_eq!, temp_dir, vec!)。
approval_keys_include_environment_id127–156 ↗
async fn approval_keys_include_environment_id()
作用:这个测试确认审批缓存或审批标识里包含环境编号。原因很简单:同一个文件路径在本地和远程环境可能不是一回事,不能混着算。
数据流:它创建一个环境编号为 remote 的补丁请求 → 调用 runtime.approval_keys → 把结果序列化成 JSON → 检查每个 key 里既有 environment_id,也有文件 path。
调用关系:它使用 test_turn_environment 构造带特定环境编号的请求,然后测试 ApplyPatchRuntime 生成审批 key 的行为。这个测试防止不同执行环境之间误复用审批结果。
调用图:调用 3 个内部函数(new_add_for_test, new, test_turn_environment);外部调用 4 个(new, assert_eq!, temp_dir, vec!)。
sandbox_cwd_uses_patch_action_cwd159–178 ↗
async fn sandbox_cwd_uses_patch_action_cwd()
作用:这个测试确认沙箱运行时使用的是补丁动作自己的工作目录。沙箱可以理解成一个安全隔离房间,进房间前必须站在正确目录,否则相对路径可能指错地方。
数据流:它创建一个新增文件的补丁请求 → 调用 runtime.sandbox_cwd → 得到沙箱应该使用的工作目录 → 检查这个目录正是 req.action.cwd。
调用关系:它用 test_turn_environment 搭环境,用 ApplyPatchAction::new_add_for_test 造补丁动作,然后直接检查 ApplyPatchRuntime 的目录选择逻辑。它守住的是沙箱启动前的路径上下文。
调用图:调用 3 个内部函数(new_add_for_test, new, test_turn_environment);外部调用 4 个(new, assert_eq!, temp_dir, vec!)。
file_system_sandbox_context_uses_active_attempt181–252 ↗
async fn file_system_sandbox_context_uses_active_attempt()
作用:这个测试确认:当确实有一次沙箱尝试时,生成的文件系统沙箱上下文会采用这次尝试里的设置,并合并额外授权。也就是说,安全隔离的配置不能凭空编,必须跟当前实际执行方案一致。
数据流:它先准备一个补丁请求,并给它额外的读写文件权限 → 再构造一个 SandboxAttempt,里面包含沙箱类型、权限、工作目录、Windows 沙箱级别、Landlock 兼容开关等 → 调用 file_system_sandbox_context_for_attempt → 得到沙箱上下文 → 再把期望的文件系统和网络权限算出来,与结果逐项比较,同时检查工作目录和平台相关开关都被保留下来。
调用关系:它用 test_turn_environment 准备请求,用权限转换函数计算“应该生效的权限”,再测试 ApplyPatchRuntime::file_system_sandbox_context_for_attempt。这个测试覆盖的是补丁工具真正进入沙箱前,权限和平台设置如何从当前尝试一路传过去。
调用图:调用 10 个内部函数(new_add_for_test, file_system_sandbox_context_for_attempt, test_turn_environment, from_read_write_roots, from_runtime_permissions, default, new, effective_file_system_sandbox_policy, effective_network_sandbox_policy, from_abs_path);外部调用 6 个(new, new, assert_eq!, temp_dir, from_ref, vec!)。
no_sandbox_attempt_has_no_file_system_context255–292 ↗
async fn no_sandbox_attempt_has_no_file_system_context()
作用:这个测试确认:如果沙箱类型是 None,也就是明确不启用沙箱,就不应该生成文件系统沙箱上下文。这样可以避免系统假装有隔离配置,造成调用方误判。
数据流:它创建一个补丁请求 → 构造一个 sandbox 为 None、权限为 Disabled 的 SandboxAttempt → 调用 file_system_sandbox_context_for_attempt → 检查结果是 None,没有产生任何文件系统沙箱上下文。
调用关系:它同样用 test_turn_environment 搭环境,但重点是测试 ApplyPatchRuntime::file_system_sandbox_context_for_attempt 的分支:有沙箱时生成上下文,没有沙箱时明确不生成。
调用图:调用 4 个内部函数(new_add_for_test, test_turn_environment, new, from_abs_path);外部调用 5 个(new, assert_eq!, temp_dir, from_ref, vec!)。
core/src/tools/runtimes/mod_tests.rs源码 ↗
这份文件不是正式功能代码,而是给运行时模块做“验收”的测试。项目在执行用户命令前,会改环境变量、套沙箱、可能接入网络代理,还会把用户 shell 的旧环境快照重新加载回来。这里逐个模拟这些场景,确认结果符合预期。比如:提权执行时不要偷偷带上 Codex 管理的代理;用户自己设置的证书和代理不能被误删;PATH 前面加的工具目录要能在快照恢复后继续生效;包装 bash/zsh/sh 命令时,单引号、额外参数、敏感密钥都不能处理错。可以把它理解成一套“出门前检查清单”:命令真正跑起来之前,环境、路径、代理、安全边界都要检查一遍,避免用户命令跑偏、泄密或连不上网。
StaticReloader::source_label38–40 ↗
fn source_label(&self) -> String
作用:给测试用的配置重载器起一个固定名字。这个名字主要用于说明配置来源,方便报错或日志里看懂。
数据流:进去的是这个测试重载器本身 → 它不读取外部文件,也不做判断 → 出来一个固定字符串“test config state”。
调用关系:它是 StaticReloader 这个测试替身的一部分。test_network_proxy 创建测试网络代理时会把 StaticReloader 塞进去,代理内部需要问配置来源时就会用到它。
StaticReloader::maybe_reload42–44 ↗
fn maybe_reload(&self) -> ConfigReloaderFuture<'_, Option<ConfigState>>
作用:假装检查配置是否需要重新加载,但在测试里永远说“不需要”。这样测试环境保持稳定,不会被外部配置变化影响。
数据流:进去的是测试重载器 → 它创建一个异步结果,直接返回“没有新配置” → 出来的是 Ok(None),表示配置不用变。
调用关系:它实现 ConfigReloader 接口,供测试网络代理持有。test_network_proxy 构造代理状态时会用到这个重载器,之后代理如果例行检查配置,就会走这里。
调用图:外部调用 1 个(pin)。
StaticReloader::reload_now46–48 ↗
fn reload_now(&self) -> ConfigReloaderFuture<'_, ConfigState>
作用:处理“强制重新加载配置”的请求,但测试里明确不支持这个动作。这样可以防止测试误以为真的能读到新配置。
数据流:进去的是测试重载器 → 它立刻构造一个错误,说明测试不支持强制重载 → 出来的是一个失败的异步结果。
调用关系:它也是 ConfigReloader 接口的一部分。test_network_proxy 装配出的代理如果被要求强制刷新配置,就会得到这个明确错误。
调用图:外部调用 2 个(pin, anyhow!)。
shell_with_snapshot51–63 ↗
fn shell_with_snapshot(
shell_type: ShellType,
shell_path: &str,
snapshot_path: AbsolutePathBuf,
) -> (Shell, AbsolutePathBuf)
作用:快速造出一对测试数据:一个 shell 信息,加上它对应的环境快照文件路径。很多测试都用它来少写重复代码。
数据流:进去的是 shell 类型、shell 程序路径、快照文件路径 → 它把路径字符串变成 PathBuf,并组装成 Shell 结构 → 出来的是“Shell 对象 + 快照绝对路径”这一对值。
调用关系:它是大量 shell 快照测试的共同小工具。比如 maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell 和后面许多测试都会先用它准备 session shell,再交给 maybe_wrap_shell_lc_with_snapshot 做命令改写。
调用图:被 19 处调用(maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override, maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell, maybe_wrap_shell_lc_with_snapshot_clears_stale_codex_git_ssh_command_without_live_command, maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv, maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes, maybe_wrap_shell_lc_with_snapshot_keeps_snapshot_path_without_override, maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive, maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args, maybe_wrap_shell_lc_with_snapshot_preserves_unset_override_variables, maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend (+9 more));外部调用 1 个(from)。
test_network_proxy65–80 ↗
async fn test_network_proxy() -> anyhow::Result<NetworkProxy>
作用:搭一个假的网络代理,专门给测试用。它不依赖真实配置文件,但有固定的 HTTP 和 SOCKS 地址,方便验证环境变量怎么被加上或清掉。
数据流:进去没有业务参数 → 它用默认代理配置和默认限制创建配置状态,再配上 StaticReloader 和固定端口 → 出来一个可用于测试的 NetworkProxy。
调用关系:explicit_escalation_prepares_exec_without_managed_network 会先调用它制造代理,再检查提权执行时是否正确避免使用 Codex 管理的网络代理。
调用图:调用 2 个内部函数(builder, with_reloader);被 1 处调用(explicit_escalation_prepares_exec_without_managed_network);外部调用 4 个(new, build_config_state, default, default)。
explicit_escalation_prepares_exec_without_managed_network83–152 ↗
async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::Result<()>
作用:检查“明确要求提权执行”的命令,不应该继续带着 Codex 管理的网络代理。否则用户以为已经脱离沙箱限制,实际网络却还被代理环境影响。
数据流:进去的是测试构造出的目录、环境变量、代理和命令 → 它先给环境套上代理,再按提权权限生成沙箱命令,最后通过 SandboxAttempt 准备执行请求 → 出来的是一个执行请求;测试确认工作目录正确、网络设置为空、Codex 代理变量被清掉,但普通自定义变量保留。
调用关系:它调用 test_network_proxy 准备代理,也调用 managed_network_for_sandbox_permissions 判断是否要托管网络,最后走 SandboxAttempt.env_for 生成实际执行请求。它覆盖的是“提权 + 不强制托管网络”这条关键路径。
调用图:调用 4 个内部函数(test_network_proxy, managed_network_for_sandbox_permissions, new, from_abs_path);外部调用 5 个(from, assert_eq!, from_ref, tempdir, vec!)。
explicit_escalation_preserves_user_ca_env155–170 ↗
fn explicit_escalation_preserves_user_ca_env()
作用:确认提权执行时,不会误删用户自己设置的证书文件环境变量。证书文件常用于公司内网或自签名 HTTPS,删掉会导致网络请求失败。
数据流:进去的是带有代理激活标记和 SSL_CERT_FILE 的环境表 → 它调用 exec_env_for_sandbox_permissions 按提权规则清理环境 → 出来还是一张环境表;测试确认 SSL_CERT_FILE 仍然是用户原来的路径。
调用关系:它直接测试 exec_env_for_sandbox_permissions 的边界行为:清理 Codex 代理相关东西时,不能把用户显式设置的证书配置一起清掉。
调用图:外部调用 2 个(from, assert_eq!)。
runtime_path_prepends_records_runtime_path_prepend174–190 ↗
fn runtime_path_prepends_records_runtime_path_prepend()
作用:检查运行时往 PATH 最前面加目录时,既要真的改当前环境,也要把这次添加记录下来,方便之后重放。
数据流:进去的是已有 PATH 和一个要加到最前面的目录 → RuntimePathPrepends.prepend 把目录放到 PATH 开头,并把目录记进 entries → 出来是更新后的环境和一条记录。
调用关系:它验证 RuntimePathPrepends 这个辅助结构的基本行为。后面的 shell 快照包装测试会依赖这些记录,把运行时加过的路径重新放回快照恢复后的 PATH 前面。
调用图:外部调用 4 个(from, from, assert_eq!, default)。
runtime_path_prepends_drops_empty_path_entries194–213 ↗
fn runtime_path_prepends_drops_empty_path_entries()
作用:检查整理 PATH 时会丢掉空项。PATH 里的空项在 Unix 上可能表示“当前目录”,保留它有安全风险,也容易造成找错程序。
数据流:进去的是包含多个冒号空段和重复目录的 PATH → prepend 会清理空段、去重,并把目标目录放到最前面 → 出来是干净的 PATH,以及只记录一次的 prepend 条目。
调用关系:它测试 RuntimePathPrepends.prepend 的安全细节。这个结果会影响所有依赖 PATH 查找程序的命令执行。
调用图:外部调用 4 个(from, from, assert_eq!, default)。
runtime_path_prepends_ignores_empty_path_entry217–233 ↗
fn runtime_path_prepends_ignores_empty_path_entry()
作用:检查如果要添加的路径本身是空的,就什么都不要做。空路径不应该被当成一个有效工具目录。
数据流:进去的是正常 PATH 和一个空 PathBuf → prepend 发现路径为空,保持环境不变,也不写记录 → 出来还是原来的 PATH 和默认的 RuntimePathPrepends。
调用关系:它保护 RuntimePathPrepends.prepend 的输入校验。这样上层即使传入空路径,也不会污染 PATH 或快照重放记录。
调用图:外部调用 4 个(from, new, assert_eq!, default)。
prepend_zsh_fork_bin_to_path_ignores_empty_parent237–251 ↗
fn prepend_zsh_fork_bin_to_path_ignores_empty_parent()
作用:检查 zsh 辅助逻辑在拿不到 zsh 所在目录时不会乱改 PATH。这里的 zsh 路径只有“zsh”这个文件名,没有父目录。
数据流:进去的是已有 PATH 和一个没有父目录的 zsh 路径 → prepend_zsh_fork_bin_to_path 判断父目录为空 → 出来 None,并且 PATH 原样不变。
调用关系:它直接测试 prepend_zsh_fork_bin_to_path。这个函数会被 apply_zsh_fork_path_prepend 这类逻辑使用,用来确保打包自带的 zsh 目录可以排在 PATH 前面。
调用图:外部调用 3 个(from, from, assert_eq!)。
apply_zsh_fork_path_prepend_uses_shell_parent255–273 ↗
fn apply_zsh_fork_path_prepend_uses_shell_parent()
作用:检查当使用打包里的 zsh 时,会把 zsh 所在的 bin 目录加到 PATH 最前面。这样后续启动相关工具时能优先找到同一套打包资源。
数据流:进去的是 PATH、空的运行时路径记录、以及 zsh 的完整路径 → 函数取 zsh 的父目录并加到 PATH 前面,同时记录这次添加 → 出来是更新后的 PATH 和一条 zsh/bin 记录。
调用关系:它测试 apply_zsh_fork_path_prepend。这个行为之后会通过 RuntimePathPrepends 在 shell 快照包装时被重放。
调用图:外部调用 4 个(from, from, assert_eq!, default)。
apply_zsh_fork_path_prepend_moves_existing_shell_parent_to_front277–301 ↗
fn apply_zsh_fork_path_prepend_moves_existing_shell_parent_to_front()
作用:检查如果 zsh 的 bin 目录原本已经在 PATH 里,也要把它移到最前面,并去掉重复项。这样既保证优先级,又避免 PATH 越来越乱。
数据流:进去的是一个已经包含 zsh/bin 且重复出现的 PATH → 函数整理 PATH,把 zsh/bin 放到最前,删除重复 → 出来是干净的新 PATH,并记录一次 prepend。
调用关系:它同样验证 apply_zsh_fork_path_prepend,但覆盖“目录已存在”的情况。这个细节能保证快照恢复和路径重放时顺序稳定。
调用图:外部调用 4 个(from, from, assert_eq!, default)。
explicit_escalation_keeps_user_proxy_env_without_codex_marker304–320 ↗
fn explicit_escalation_keeps_user_proxy_env_without_codex_marker()
作用:确认提权执行时,如果代理变量是用户自己设置的,而不是 Codex 标记的代理,就要保留。否则会误伤用户自己的网络配置。
数据流:进去的是包含 HTTP_PROXY 和 CUSTOM_ENV 的环境表 → exec_env_for_sandbox_permissions 按提权规则处理 → 出来仍然保留用户代理和普通自定义变量。
调用关系:它测试 exec_env_for_sandbox_permissions 对代理变量的分辨能力:只清理 Codex 自己加的代理痕迹,不粗暴删除所有代理设置。
调用图:外部调用 2 个(from, assert_eq!)。
maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell323–348 ↗
fn maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell()
作用:检查 bash -lc 这类命令会被改写成先用用户会话的 shell 加载快照,再执行原命令。这样命令能继承用户登录 shell 的环境。
数据流:进去的是一个 bash -lc echo 命令、zsh 会话 shell、快照文件路径和空环境 → maybe_wrap_shell_lc_with_snapshot 生成新 argv → 出来以 /bin/zsh -c 开头,脚本里先 source 快照,再 exec 原来的 bash 命令。
调用关系:它用 shell_with_snapshot 准备测试 shell,核心检查 maybe_wrap_shell_lc_with_snapshot 的包装行为是否走“用户 shell 启动器”这条路。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 7 个(new, assert!, assert_eq!, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes351–373 ↗
fn maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes()
作用:检查命令里有单引号时,包装脚本仍然能正确引用。否则像 echo 'hello' 这样的普通命令都可能被拆坏。
数据流:进去的是包含单引号的 bash -lc 命令和快照信息 → 包装函数把命令安全地嵌进 shell 脚本 → 出来的脚本里单引号被转义成 shell 能理解的形式。
调用关系:它针对 maybe_wrap_shell_lc_with_snapshot 的命令引用规则。shell_with_snapshot 只负责准备快照环境,真正被验证的是包装字符串是否安全。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 6 个(new, assert!, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell376–401 ↗
fn maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell()
作用:检查当用户会话 shell 是 bash 时,包装命令也用 bash 来加载快照。这样恢复环境时更接近用户原本的 shell 行为。
数据流:进去的是原本要运行 zsh -lc 的命令、bash 会话 shell 和快照 → 包装函数选择 /bin/bash 作为启动 shell → 出来的 argv 是 /bin/bash -c,并在脚本里执行原来的 zsh 命令。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 会尊重 session shell。shell_with_snapshot 提供 bash 类型,包装函数据此选择启动程序。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 7 个(new, assert!, assert_eq!, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_uses_sh_bootstrap_shell404–429 ↗
fn maybe_wrap_shell_lc_with_snapshot_uses_sh_bootstrap_shell()
作用:检查当用户会话 shell 是 sh 时,包装命令会用 sh 启动。即使原命令是 bash,也先按用户会话环境恢复。
数据流:进去的是 bash -lc 命令、sh 会话 shell 和快照 → 包装函数选 /bin/sh -c 作为外层 → 出来的脚本先加载快照,再 exec 原 bash 命令。
调用关系:它覆盖 maybe_wrap_shell_lc_with_snapshot 的另一种 shell 类型选择,确保 bash、zsh、sh 都按会话 shell 来启动。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 7 个(new, assert!, assert_eq!, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args432–459 ↗
fn maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args()
作用:检查原命令后面额外带的参数不会在包装后丢失。shell -c 后面的参数常被脚本当作 $0、$1 使用,丢了会改变命令行为。
数据流:进去的是 bash -lc 后面还带 arg0、arg1 的命令 → 包装函数把原命令和尾随参数一起安全写进 exec 调用 → 出来的脚本仍然包含 arg0 和 arg1。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 不只是保留主命令字符串,也保留 argv 中后续参数。shell_with_snapshot 只提供测试快照。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 6 个(new, assert!, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_restores_explicit_override_precedence462–498 ↗
fn maybe_wrap_shell_lc_with_snapshot_restores_explicit_override_precedence()
作用:检查显式传入的环境变量覆盖值,比快照里的旧值优先。比如工作区指定 TEST_ENV_SNAPSHOT=worktree,就不能被快照里的 global 覆盖。
数据流:进去的是含旧环境的快照、一个读取变量的命令、显式覆盖表和当前环境表 → 包装函数生成脚本,运行时先加载快照再恢复覆盖变量 → 出来的实际输出是 worktree|from_snapshot,说明覆盖值赢了,快照独有变量也保留。
调用关系:它调用 shell_with_snapshot 准备 bash 快照,然后真的用 Command 运行改写后的命令。它验证 maybe_wrap_shell_lc_with_snapshot 生成的脚本在真实 shell 中行为正确。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(from, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env501–532 ↗
fn maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env()
作用:检查 CODEX_THREAD_ID 这类当前进程里的运行标识,会在加载快照后恢复成最新值。否则嵌套运行时可能误用父会话的线程 ID。
数据流:进去的是快照里旧的 CODEX_THREAD_ID、当前环境里的 nested-thread、以及打印变量的命令 → 包装脚本加载快照后重新从当前环境恢复该变量 → 出来的 stdout 是 nested-thread。
调用关系:它用真实 Command 执行 maybe_wrap_shell_lc_with_snapshot 生成的命令,专门验证快照恢复后对 Codex 自身环境变量的修正。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 9 个(from, new, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_restores_proxy_env_from_process_env535–581 ↗
fn maybe_wrap_shell_lc_with_snapshot_restores_proxy_env_from_process_env()
作用:检查代理已激活时,快照里的旧代理地址不会覆盖当前进程的新代理地址。代理端口变了还用旧值,会导致网络访问失败。
数据流:进去的是写着旧代理地址的快照、打印多个代理变量的命令、以及运行时通过 env 传入的新代理地址 → 包装脚本恢复当前进程里的代理变量 → 出来 PIP_PROXY、HTTP_PROXY、http_proxy 都是新地址;普通 GIT_SSH_COMMAND 在这个测试里仍显示快照值。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 生成的脚本如何处理网络代理变量。shell_with_snapshot 负责造快照,Command 负责跑真实 shell 看结果。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(new, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_refreshes_codex_proxy_git_ssh_command585–625 ↗
fn maybe_wrap_shell_lc_with_snapshot_refreshes_codex_proxy_git_ssh_command()
作用:在 macOS 上检查 Codex 自己生成的 Git SSH 代理命令会刷新成当前值。旧的 SOCKS 端口如果留在快照里,git 走 SSH 时会连错代理。
数据流:进去的是快照里的旧 Codex Git SSH 命令、当前环境里的新命令、以及打印该变量的命令 → 包装脚本识别这是 Codex 标记过的变量,并用当前环境值覆盖 → 出来是 fresh_command。
调用关系:它只在 macOS 编译运行,测试 maybe_wrap_shell_lc_with_snapshot 对 PROXY_GIT_SSH_COMMAND_ENV_KEY 的特殊处理,并用 shell_with_snapshot 和真实 Command 验证结果。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 9 个(new, assert!, assert_eq!, new, default, format!, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_restores_custom_git_ssh_command629–667 ↗
fn maybe_wrap_shell_lc_with_snapshot_restores_custom_git_ssh_command()
作用:在 macOS 上检查如果用户自己设置了 Git SSH 命令,就应该用用户当前的命令,而不是快照里 Codex 的旧代理命令。
数据流:进去的是快照里的旧 Codex 标记命令、当前环境里的自定义 Git SSH 命令 → 包装脚本加载快照后恢复当前环境值 → 出来是用户自定义命令。
调用关系:它测试 maybe_wrap_shell_lc_with_snapshot 不会把 Codex 的旧代理配置强压到用户自定义配置上。shell_with_snapshot 提供快照,Command 运行结果。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 9 个(new, assert!, assert_eq!, new, default, format!, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_clears_stale_codex_git_ssh_command_without_live_command671–710 ↗
fn maybe_wrap_shell_lc_with_snapshot_clears_stale_codex_git_ssh_command_without_live_command()
作用:在 macOS 上检查如果快照里有旧的 Codex Git SSH 代理命令,但当前环境已经没有这个变量,就应该清掉它。否则会凭空复活一个过期代理设置。
数据流:进去的是含旧代理命令的快照、当前环境中移除了该变量、以及检测变量是否存在的命令 → 包装脚本加载快照后发现当前没有 live command,于是 unset 该变量 → 出来打印 unset。
调用关系:它覆盖 maybe_wrap_shell_lc_with_snapshot 的清理分支。这个分支和前两个 macOS 测试一起保证 Git SSH 代理不会因为快照而变旧、误留或误覆盖。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 9 个(new, assert!, assert_eq!, new, default, format!, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive713–748 ↗
fn maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive()
作用:检查当 Codex 代理没有激活时,快照里的用户代理变量应该保留。用户自己写在 shell 配置里的 HTTP_PROXY 不应被当成 Codex 代理清掉。
数据流:进去的是快照中含用户 HTTP_PROXY、当前运行命令时移除所有 Codex 代理变量 → 包装脚本加载快照后不做 Codex 代理恢复/清理 → 出来 stdout 仍是用户代理地址。
调用关系:它测试 maybe_wrap_shell_lc_with_snapshot 对“代理未激活”状态的判断。Command 运行前显式移除 PROXY_ENV_KEYS,用来模拟没有 live Codex 代理。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(new, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_restores_live_env_when_snapshot_proxy_active751–799 ↗
fn maybe_wrap_shell_lc_with_snapshot_restores_live_env_when_snapshot_proxy_active()
作用:检查快照里说代理激活过,但当前环境没有代理激活时,要以当前环境为准。否则旧快照会把已经关闭的代理重新打开。
数据流:进去的是带 PROXY_ACTIVE 和旧代理变量的快照、当前环境只保留用户 HTTP_PROXY 并移除 PIP_PROXY 与激活标记 → 包装脚本加载快照后恢复当前环境状态 → 出来显示 PIP_PROXY 未设置、HTTP_PROXY 是用户值、激活标记未设置。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 的代理状态同步规则:快照不能覆盖当前真实环境。shell_with_snapshot 准备快照,Command 运行并检查输出。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 10 个(from, new, assert!, assert_eq!, new, default, format!, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_keeps_snapshot_path_without_override802–832 ↗
fn maybe_wrap_shell_lc_with_snapshot_keeps_snapshot_path_without_override()
作用:检查没有显式 PATH 覆盖时,快照里的 PATH 会被保留。这样用户登录 shell 捕获到的路径设置能继续影响命令。
数据流:进去的是快照 PATH=/snapshot/bin、没有环境覆盖、以及打印 PATH 的命令 → 包装脚本加载快照 → 出来 stdout 是 /snapshot/bin。
调用关系:它测试 maybe_wrap_shell_lc_with_snapshot 的默认 PATH 行为。后续测试会再检查有覆盖和有运行时 prepend 时的优先级。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(new, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override835–867 ↗
fn maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override()
作用:检查如果调用方明确传了 PATH 覆盖值,就要压过快照里的 PATH。工作区或执行器指定的 PATH 通常比旧快照更应该生效。
数据流:进去的是快照 PATH=/snapshot/bin、显式覆盖 PATH=/worktree/bin、当前环境也带这个值 → 包装脚本加载快照后恢复覆盖值 → 出来 stdout 是 /worktree/bin。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 的 PATH 优先级规则:显式覆盖高于快照。它使用 shell_with_snapshot 准备数据,并用真实 Command 验证。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(from, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_preserves_package_path_prepend871–881 ↗
fn maybe_wrap_shell_lc_with_snapshot_preserves_package_path_prepend() -> anyhow::Result<()>
作用:检查运行时为了找到打包工具而加到 PATH 前面的目录,在加载快照后仍然排在最前。否则快照 PATH 可能把项目自带工具挤掉。
数据流:进去没有直接构造所有细节,而是调用辅助探针函数且不传显式 PATH 覆盖 → 辅助函数创建快照、添加运行时 PATH 前缀并执行命令 → 出来 stdout 应该是“包目录:/snapshot/bin”。
调用关系:它把细节交给 run_snapshot_path_probe_with_runtime_path_prepend,自己只断言结果。它验证 maybe_wrap_shell_lc_with_snapshot 和 RuntimePathPrepends 的配合。
调用图:调用 1 个内部函数(run_snapshot_path_probe_with_runtime_path_prepend);外部调用 2 个(new, assert_eq!)。
maybe_wrap_shell_lc_with_snapshot_applies_runtime_path_prepend_after_explicit_path_override885–897 ↗
fn maybe_wrap_shell_lc_with_snapshot_applies_runtime_path_prepend_after_explicit_path_override() -> anyhow::Result<()>
作用:检查即使有显式 PATH 覆盖,运行时追加到最前的工具目录也仍然要保留在最前。也就是“工具目录”优先于“显式 PATH”,显式 PATH 又优先于快照 PATH。
数据流:进去的是显式 PATH=/worktree/bin → 辅助探针创建快照 PATH=/snapshot/bin,并把包目录 prepend 到当前 PATH → 出来 stdout 应该是“包目录:/worktree/bin”。
调用关系:它调用 run_snapshot_path_probe_with_runtime_path_prepend 复用完整场景,验证 maybe_wrap_shell_lc_with_snapshot 的 PATH 三层优先级。
调用图:调用 1 个内部函数(run_snapshot_path_probe_with_runtime_path_prepend);外部调用 2 个(from, assert_eq!)。
run_snapshot_path_probe_with_runtime_path_prepend900–941 ↗
fn run_snapshot_path_probe_with_runtime_path_prepend(
explicit_env_overrides: HashMap<String, String>,
) -> anyhow::Result<(String, PathBuf)>
作用:这是两个 PATH 测试共用的小型实验台:创建快照、添加运行时 PATH 前缀、包装命令、真实执行,然后把看到的 PATH 返回。
数据流:进去的是一张可选的显式环境覆盖表 → 它建临时目录和快照文件,构造 bash -lc 打印 PATH 的命令,给当前环境 prepend 一个包目录,再调用 maybe_wrap_shell_lc_with_snapshot 并执行 → 出来是命令打印的 PATH 字符串和包目录路径。
调用关系:maybe_wrap_shell_lc_with_snapshot_preserves_package_path_prepend 和 maybe_wrap_shell_lc_with_snapshot_applies_runtime_path_prepend_after_explicit_path_override 都调用它。它把重复的真实 shell 执行流程集中到一个地方。
调用图:调用 1 个内部函数(shell_with_snapshot);被 2 处调用(maybe_wrap_shell_lc_with_snapshot_applies_runtime_path_prepend_after_explicit_path_override, maybe_wrap_shell_lc_with_snapshot_preserves_package_path_prepend);外部调用 8 个(from, from_utf8_lossy, assert!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend945–991 ↗
fn maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend()
作用:检查 zsh fork 相关的 bin 目录 prepend,也能在快照恢复后继续排在 PATH 最前。这个目录通常来自打包资源,不能被快照 PATH 覆盖。
数据流:进去的是快照 PATH、模拟出来的 zsh 路径、当前 PATH 和空的运行时记录 → apply_zsh_fork_path_prepend 先把 zsh/bin 加到当前 PATH 并记录,包装函数再重放这个 prepend → 出来 stdout 是“zsh/bin:/snapshot/bin”。
调用关系:它把 apply_zsh_fork_path_prepend 和 maybe_wrap_shell_lc_with_snapshot 放在一起测,确认路径记录不仅当场生效,也能穿过 shell 快照恢复流程。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 9 个(from, new, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv994–1036 ↗
fn maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv()
作用:检查敏感的环境覆盖值不会被直接写进命令参数里。命令参数 argv 可能被系统工具看到,如果把 API key 写进去就有泄密风险。
数据流:进去的是快照里的旧 OPENAI_API_KEY、显式覆盖里的 super-secret-value、以及打印密钥的命令 → 包装函数生成脚本时只引用变量名,不把秘密值写入 argv → 出来的 rewritten 脚本不含秘密值;真实执行时从环境取到秘密值并打印。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 的安全设计:恢复覆盖变量时不能把值硬编码进脚本。shell_with_snapshot 准备快照,Command 用 env 传入秘密值。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 8 个(from, assert!, assert_eq!, new, default, write, tempdir, vec!)。
maybe_wrap_shell_lc_with_snapshot_preserves_unset_override_variables1039–1074 ↗
fn maybe_wrap_shell_lc_with_snapshot_preserves_unset_override_variables()
作用:检查如果某个变量被列为显式覆盖,但当前实际环境里并没有它,加载快照后也应该保持“未设置”。不能因为快照里有旧值就把它复活。
数据流:进去的是快照里含 CODEX_TEST_UNSET_OVERRIDE、显式覆盖表里也提到这个变量,但当前运行时 env_remove 它 → 包装脚本加载快照后发现当前环境没有该变量,于是 unset → 出来打印 unset。
调用关系:它验证 maybe_wrap_shell_lc_with_snapshot 对“覆盖变量为空/未设置”的处理。这个测试和显式覆盖优先级测试一起保证快照不会覆盖调用方真实意图。
调用图:调用 1 个内部函数(shell_with_snapshot);外部调用 9 个(from, new, assert!, assert_eq!, new, default, write, tempdir, vec!)。
core/src/tools/runtimes/shell/unix_escalation_tests.rs源码 ↗
这个文件像一套安全闸机的验收清单。项目里运行 shell 命令时,可能会遇到很多情况:命令外面包了一层 bash/zsh,策略规则要求审批,沙箱限制文件或网络访问,用户预先批准了额外权限,甚至还有 hook(外部脚本回调)可以直接决定是否允许。这里的测试把这些边界情况逐个摆出来,确认系统做出的决定符合预期。它还准备了一些小工具函数,比如拼出不同系统下的绝对路径、构造只读沙箱规则、构造“禁止读 .env 文件”的规则。重点不是测试命令真的完成了什么业务,而是测试安全判断是否稳定:该保留的审批理由不能丢,该去掉的代理环境变量必须去掉,该按内层命令匹配策略时不能只看外层 shell。
host_absolute_path50–60 ↗
fn host_absolute_path(segments: &[&str]) -> String
作用:生成一条适合当前操作系统的绝对路径。测试里不能硬写 /usr/bin/git 这种路径,因为 Windows 和 Unix 的根目录写法不一样。
数据流:输入是一组路径片段,比如 usr、bin、git → 它先按系统选择根目录,Windows 用 C:\,其他系统用 /,再把片段一个个接上 → 输出是一条字符串形式的绝对路径。
调用关系:很多测试需要假装有一个真实的主机程序路径时会先用它造路径。test_sandbox_cwd 也会借它生成测试工作目录。它是测试里的路径小帮手,不参与真正执行命令。
调用图:被 9 处调用(commands_for_intercepted_exec_policy_parses_plain_shell_wrappers, denied_reads_keep_granular_sandbox_rejection_for_escalation, denied_reads_keep_prefix_rule_allow_inside_sandbox, evaluate_intercepted_exec_policy_matches_inner_shell_commands_when_enabled, evaluate_intercepted_exec_policy_uses_wrapper_command_when_shell_wrapper_parsing_disabled, intercepted_exec_policy_rejects_disallowed_host_executable_mapping, intercepted_exec_policy_treats_preapproved_additional_permissions_as_default, intercepted_exec_policy_uses_host_executable_mappings, test_sandbox_cwd);外部调用 2 个(from, cfg!)。
starlark_string62–64 ↗
fn starlark_string(value: &str) -> String
作用:把普通字符串转成能安全写进 Starlark 策略文本里的字符串片段。Starlark 可以理解成一种用来写策略规则的小脚本语言。
数据流:输入是一段路径或文本 → 它把反斜杠和双引号加上转义,避免这些字符破坏策略文本格式 → 输出是转义后的字符串。
调用关系:当测试现场拼接策略规则、特别是把主机路径写进 host_executable 或 prefix_rule 时会用到它。它保证测试规则文本不会因为路径里有特殊字符而解析失败。
调用图:被 3 处调用(denied_reads_keep_prefix_rule_allow_inside_sandbox, intercepted_exec_policy_rejects_disallowed_host_executable_mapping, intercepted_exec_policy_uses_host_executable_mappings)。
read_only_file_system_sandbox_policy66–73 ↗
fn read_only_file_system_sandbox_policy() -> FileSystemSandboxPolicy
作用:构造一个“整个文件系统只能读”的沙箱文件规则。沙箱可以理解成给命令划出的安全活动范围。
数据流:它不需要外部输入 → 创建一条规则:根目录允许读取 → 输出一个受限制的文件系统沙箱策略。
调用关系:多个提权和 hook 测试会用它作为基础环境。这样测试可以明确知道:默认情况下命令只能读文件,想写文件或突破限制就必须走审批或提权逻辑。
调用图:调用 1 个内部函数(restricted);被 4 处调用(execve_permission_request_hook_short_circuits_prompt, preapproved_additional_permissions_escalate_intercepted_exec, shell_request_escalation_execution_is_explicit, unsandboxed_intercepted_exec_strips_managed_network_env);外部调用 1 个(vec!)。
denied_read_file_system_sandbox_policy75–90 ↗
fn denied_read_file_system_sandbox_policy() -> FileSystemSandboxPolicy
作用:构造一个更细的沙箱规则:大部分地方可读,但 .env 文件不允许读。.env 常放密钥,所以这是很典型的敏感文件限制。
数据流:它不需要外部输入 → 创建两条规则:根目录可读,同时匹配 **/*.env 的文件禁止访问 → 输出这个带有拒绝项的沙箱策略。
调用关系:后面的测试用它检查:即使沙箱里有“禁止读某些文件”的规则,策略允许、审批拒绝这些判断也不能被搞混。
调用图:调用 1 个内部函数(restricted);被 2 处调用(denied_reads_keep_granular_sandbox_rejection_for_escalation, denied_reads_keep_prefix_rule_allow_inside_sandbox);外部调用 1 个(vec!)。
test_sandbox_cwd92–94 ↗
fn test_sandbox_cwd() -> AbsolutePathBuf
作用:给测试准备一个固定的沙箱工作目录。工作目录就是命令运行时所在的文件夹。
数据流:它不接收参数 → 先用 host_absolute_path 生成当前系统下的 /workspace 或类似路径,再转成项目使用的绝对路径类型 → 输出这个测试工作目录。
调用关系:需要构造命令执行环境的异步测试会调用它。它把“测试命令从哪里运行”这件事统一起来,避免每个测试自己拼路径。
调用图:调用 2 个内部函数(host_absolute_path, try_from);被 4 处调用(denied_reads_keep_granular_sandbox_rejection_for_escalation, denied_reads_keep_prefix_rule_allow_inside_sandbox, preapproved_additional_permissions_escalate_intercepted_exec, unsandboxed_intercepted_exec_strips_managed_network_env)。
execve_prompt_rejection_keeps_prefix_rules_on_rules_flag97–111 ↗
fn execve_prompt_rejection_keeps_prefix_rules_on_rules_flag()
作用:确认当策略规则要求审批,但细粒度审批配置里关闭了“规则审批”时,系统会拒绝并给出正确原因。execve 是操作系统启动新程序的接口,这里指被拦截的程序启动请求。
数据流:输入是一组测试写死的审批配置和“来自前缀规则”的决策来源 → 调用被测逻辑判断是否应该拒绝弹审批 → 结果必须是说明 rules 开关关闭的错误文字。
调用关系:这是直接验证审批拒绝原因的小测试。它不再把工作交给其他本文件函数,只用断言确认核心逻辑没有把“规则审批”和别的审批开关混淆。
调用图:外部调用 1 个(assert_eq!)。
execve_prompt_rejection_keeps_unmatched_commands_on_sandbox_flag114–128 ↗
fn execve_prompt_rejection_keeps_unmatched_commands_on_sandbox_flag()
作用:确认没有匹配到具体规则、只能靠沙箱兜底时,如果关闭了“沙箱审批”,系统会拒绝并说明原因。
数据流:输入是关闭 sandbox_approval 的细粒度审批配置,以及“未匹配命令兜底”的决策来源 → 被测逻辑判断拒绝原因 → 输出必须是说明沙箱审批被关闭的文字。
调用关系:它和上一个测试是一对:分别检查两类审批来源。目的是保证用户看到的拒绝原因准确,不会误导排查。
调用图:外部调用 1 个(assert_eq!)。
approval_sandbox_permissions_only_downgrades_preapproved_additional_permissions131–153 ↗
fn approval_sandbox_permissions_only_downgrades_preapproved_additional_permissions()
作用:确认只有“额外权限已经预先批准”这种情况,才会把沙箱权限请求降级成默认权限。换句话说,已经批准过的权限不要重复要求审批。
数据流:输入是不同的沙箱权限状态和“额外权限是否预先批准”的布尔值 → 调用被测函数计算审批时实际使用的沙箱权限 → 输出应分别保持、降级或不变。
调用关系:后面的 intercepted_exec_policy_treats_preapproved_additional_permissions_as_default 会在更完整的策略评估里用到同类行为。这个测试先单独确认转换规则本身是对的。
调用图:外部调用 1 个(assert_eq!)。
extract_shell_script_preserves_login_flag156–173 ↗
fn extract_shell_script_preserves_login_flag()
作用:确认从 zsh 命令行里抽出真正脚本时,不会丢掉“是否登录 shell”的信息。登录 shell 可以理解成会加载更多用户环境配置的 shell。
数据流:输入是两组命令参数:一种用 -lc,一种用 -c → 被测函数解析出 shell 程序、脚本文本和登录标记 → 输出必须分别标记为登录和非登录。
调用关系:它测试的是命令解析的基础能力。后面测试 shell 包装命令和策略匹配时,都依赖这种解析不要看错命令结构。
调用图:外部调用 1 个(assert_eq!)。
extract_shell_script_supports_wrapped_command_prefixes176–209 ↗
fn extract_shell_script_supports_wrapped_command_prefixes()
作用:确认命令前面套了常见包装器时,系统仍能找到里面真正的 shell 脚本。包装器就像快递外箱,真正要看的东西在里面。
数据流:输入是带 /usr/bin/env 环境变量前缀的命令,以及带 sandbox-exec 沙箱包装的命令 → 被测函数跳过外层包装,解析内层 zsh 命令 → 输出真正的程序名、脚本和登录标记。
调用关系:它保证策略评估不会只看到外壳。后面的 commands_for_intercepted_exec_policy_parses_plain_shell_wrappers 会进一步检查能不能把脚本拆成具体命令。
调用图:外部调用 1 个(assert_eq!)。
extract_shell_script_rejects_unsupported_shell_invocation212–227 ↗
fn extract_shell_script_rejects_unsupported_shell_invocation()
作用:确认遇到不支持的 shell 调用格式时,系统会明确拒绝,而不是硬猜。安全代码里“猜错”可能比直接失败更危险。
数据流:输入是一组 sandbox-exec -fc ... 形式的参数 → 被测解析函数尝试理解但发现格式不符合预期 → 输出是拒绝错误,并且错误原因必须是固定文字。
调用关系:它直接调用 extract_shell_script。这个测试守住了解析边界:支持的格式可以通过,不支持的格式必须安全失败。
调用图:外部调用 3 个(assert!, assert_eq!, extract_shell_script)。
join_program_and_argv_replaces_original_argv_zero230–245 ↗
fn join_program_and_argv_replaces_original_argv_zero()
作用:确认拼命令参数时,会用解析出来的真实程序路径替换原来的第一个参数。第一个参数常叫 argv[0],通常表示程序名。
数据流:输入是真实程序路径 /tmp/tool 和原始参数数组,比如 ./tool --flag value → 被测函数把第一个参数换成真实绝对路径,保留后面的参数 → 输出新的参数数组。
调用关系:它测试的是执行前整理命令参数的细节。这个行为能避免策略或执行器继续使用不可靠的相对程序名。
调用图:外部调用 1 个(assert_eq!)。
commands_for_intercepted_exec_policy_parses_plain_shell_wrappers248–263 ↗
fn commands_for_intercepted_exec_policy_parses_plain_shell_wrappers()
作用:确认被拦截的 shell 命令如果只是普通包装器,策略系统能看到里面的具体命令,比如 git status 和 pwd。
数据流:输入是一个 bash 程序路径和参数 -lc 'git status && pwd' → 被测函数解析 shell 脚本,把复合命令拆开 → 输出候选命令列表,并标记没有用复杂解析。
调用关系:它先用 host_absolute_path 造 bash 路径,再调用 commands_for_intercepted_exec_policy。这是策略匹配前的一步:先把外层 shell 拆开,后面才能按真实命令套规则。
调用图:调用 2 个内部函数(host_absolute_path, try_from);外部调用 3 个(assert!, assert_eq!, commands_for_intercepted_exec_policy)。
map_exec_result_preserves_stdout_and_stderr266–283 ↗
fn map_exec_result_preserves_stdout_and_stderr()
作用:确认执行结果转换时,不会把标准输出、标准错误和合并输出弄丢或弄混。标准输出通常是正常结果,标准错误通常是错误信息。
数据流:输入是一个假的执行结果,里面有 stdout、stderr、合并输出、耗时和退出码 → 被测函数把它转成上层工具返回格式 → 输出里三类文本必须原样保留。
调用关系:它只测试结果映射这一小段。真正命令执行后也会走类似转换,所以这里保证用户最终看到的输出不会被错误加工。
调用图:外部调用 3 个(from_millis, assert_eq!, map_exec_result)。
shell_request_escalation_execution_is_explicit286–355 ↗
fn shell_request_escalation_execution_is_explicit()
作用:确认 shell 请求提权时,系统会明确选择三种路线之一:用本轮默认设置、不进沙箱执行、或用解析后的额外权限执行。
数据流:输入包括原始权限配置、只读或可写沙箱策略、网络限制、以及用户请求的额外文件写权限 → 被测函数判断应该怎样执行 → 输出分别是默认执行、无沙箱执行,或带解析权限配置的执行方式。
调用关系:它用 read_only_file_system_sandbox_policy 准备只读环境,并直接测试 CoreShellActionProvider::shell_request_escalation_execution。后面的完整提权测试会验证这个决定在实际策略流程里是否被采用。
调用图:调用 4 个内部函数(read_only_file_system_sandbox_policy, from_read_write_roots, from_runtime_permissions, restricted);外部调用 3 个(default, assert_eq!, vec!)。
unsandboxed_intercepted_exec_strips_managed_network_env358–404 ↗
async fn unsandboxed_intercepted_exec_strips_managed_network_env() -> anyhow::Result<()>
作用:确认命令被提升到“无沙箱执行”时,会移除项目自己管理网络代理用的环境变量。否则无沙箱命令可能还带着本该只在受控网络里使用的代理设置。
数据流:输入是一个执行器、curl 程序路径、命令参数、工作目录,以及带有代理标记和代理地址的环境变量表 → 被测准备执行函数生成真正要执行的命令环境 → 输出的环境变量里必须不再包含这些受管网络代理键。
调用关系:它用 test_sandbox_cwd 和 read_only_file_system_sandbox_policy 搭出执行环境,然后调用执行器的 prepare_escalated_exec。这是提权执行前的清理检查。
调用图:调用 4 个内部函数(read_only_file_system_sandbox_policy, test_sandbox_cwd, workspace_write, from_absolute_path);外部调用 5 个(new, new, assert!, format!, vec!)。
preapproved_additional_permissions_escalate_intercepted_exec407–457 ↗
async fn preapproved_additional_permissions_escalate_intercepted_exec() -> anyhow::Result<()>
作用:确认用户已经预先批准的额外权限,会让被拦截命令按“带额外权限的沙箱”去执行,而不是重新弹审批或无沙箱乱跑。
数据流:输入是测试会话、请求的 /tmp/output 写权限、基础工作区权限和只读沙箱策略 → 先算出合并后的有效权限,再构造 shell 动作提供者 → 调用提权策略后,输出应是“用解析后的权限配置执行”的提权决定。
调用关系:它调用 make_session_and_context 建测试会话,用 effective_permission_profile 合并权限,再把判断交给 EscalationPolicy::determine_action。它验证前面单独测试过的权限降级/合并规则能在完整流程里生效。
调用图:调用 8 个内部函数(make_session_and_context, read_only_file_system_sandbox_policy, test_sandbox_cwd, from_read_write_roots, workspace_write, effective_permission_profile, new, from_absolute_path);外部调用 11 个(new, default, from_secs, new, assert_eq!, empty, ResolvedPermissionProfile, Escalate, Permissions, determine_action (+1 more))。
execve_permission_request_hook_short_circuits_prompt460–609 ↗
async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Result<()>
作用:确认权限请求 hook 可以直接给出允许决定,从而跳过正常的人工审批提示。hook 是用户配置的外部脚本回调,像是在安全门口加了一个可编程门卫。
数据流:输入是一个临时写好的 hook 脚本、可信 hook 配置、只读权限环境和一条 touch 命令 → 测试把 hook 注册到会话里,再让提权策略判断命令 → 输出应是无沙箱提权执行,并且 hook 日志里记录的命令内容必须正确。
调用关系:这是本文件最完整的集成式测试之一。它先准备文件、配置、hook 信任状态和 provider,之后调用 EscalationPolicy::determine_action,最后读取 hook 日志确认系统确实把权限请求交给了 hook。
调用图:调用 10 个内部函数(allow_any, make_session_and_context, read_only_file_system_sandbox_policy, new, from_runtime_permissions, read_only, shlex_join, new, from_absolute_path, try_from);外部调用 22 个(new, from_secs, new, assert!, assert_eq!, list_hooks, empty, format!, default, from_value (+12 more))。
evaluate_intercepted_exec_policy_uses_wrapper_command_when_shell_wrapper_parsing_disabled612–660 ↗
fn evaluate_intercepted_exec_policy_uses_wrapper_command_when_shell_wrapper_parsing_disabled()
作用:确认关闭“解析 shell 包装器”开关时,策略评估只看外层 shell 命令,而不会钻进去看 npm publish。
数据流:输入是一条要求 npm publish 弹审批的策略、zsh 程序路径、zsh -lc 'npm publish' 参数,以及关闭 shell 内层解析的上下文 → 被测策略评估按整个外层命令匹配 → 输出应是启发式允许,而不是命中 npm publish 的提示规则。
调用关系:它用 host_absolute_path 准备 zsh 路径,再调用 evaluate_intercepted_exec_policy。这个测试证明配置开关真的会改变策略看到的命令层级。
调用图:调用 4 个内部函数(host_absolute_path, new, read_only, try_from);外部调用 2 个(assert!, evaluate_intercepted_exec_policy)。
evaluate_intercepted_exec_policy_matches_inner_shell_commands_when_enabled663–700 ↗
fn evaluate_intercepted_exec_policy_matches_inner_shell_commands_when_enabled()
作用:确认打开“解析 shell 包装器”开关后,策略能看见 shell 里面真正执行的 npm publish,并按规则要求审批。
数据流:输入是同样要求 npm publish 弹审批的策略、bash 程序路径、bash -lc 'npm publish' 参数,以及开启内层解析的上下文 → 被测策略评估解析内层命令并匹配前缀规则 → 输出是 Prompt,并记录命中了 npm publish 规则。
调用关系:它和前一个测试形成对照。两者都调用 evaluate_intercepted_exec_policy,区别只在开关是否启用,用来保证策略评估可控、可预期。
调用图:调用 4 个内部函数(host_absolute_path, new, read_only, try_from);外部调用 2 个(assert_eq!, evaluate_intercepted_exec_policy)。
intercepted_exec_policy_uses_host_executable_mappings703–746 ↗
fn intercepted_exec_policy_uses_host_executable_mappings()
作用:确认策略里的主机可执行文件映射能生效:即使命令叫 git,系统也能确认它对应的是允许映射的真实路径。
数据流:输入是一条 git status 需要审批的规则、一条 git 到真实路径的映射、实际程序路径和参数 → 被测策略评估把路径和命令名对应起来 → 输出是命中前缀规则、要求审批,并记录解析到的真实程序路径。
调用关系:它用 host_absolute_path 和 starlark_string 构造可解析的策略文本,然后调用 evaluate_intercepted_exec_policy。最后还检查 CoreShellActionProvider::decision_driven_by_policy,确认这个决定确实来自策略规则。
调用图:调用 5 个内部函数(host_absolute_path, starlark_string, new, read_only, try_from);外部调用 4 个(assert!, assert_eq!, format!, evaluate_intercepted_exec_policy)。
denied_reads_keep_prefix_rule_allow_inside_sandbox749–793 ↗
async fn denied_reads_keep_prefix_rule_allow_inside_sandbox() -> anyhow::Result<()>
作用:确认即使文件系统策略里有“禁止读 .env”这种拒绝规则,只要命令被前缀策略明确允许,它仍然应该在沙箱内运行,而不是被错误升级或拒绝。
数据流:输入是允许某个 cat 路径的策略、带 .env 禁读项的沙箱策略、测试会话和命令参数 → 构造 provider 后交给提权策略判断 → 输出应是普通运行 Run。
调用关系:它用 denied_read_file_system_sandbox_policy 准备带拒绝项的沙箱,用 test_sandbox_cwd 准备工作目录,再调用 EscalationPolicy::determine_action。它证明“沙箱内部的文件限制”和“是否允许启动命令”是两层不同判断。
调用图:调用 9 个内部函数(make_session_and_context, denied_read_file_system_sandbox_policy, host_absolute_path, starlark_string, test_sandbox_cwd, new, from_runtime_permissions, new, try_from);外部调用 6 个(new, from_secs, new, assert_eq!, format!, determine_action)。
denied_reads_keep_granular_sandbox_rejection_for_escalation796–840 ↗
async fn denied_reads_keep_granular_sandbox_rejection_for_escalation() -> anyhow::Result<()>
作用:确认当命令需要提权,但细粒度审批配置关闭了沙箱审批时,系统会拒绝执行,而不是因为文件系统里有禁读项就走别的分支。
数据流:输入是带 .env 禁读项的沙箱策略、关闭 sandbox_approval 的审批配置、测试会话和 printf 命令 → 提权策略尝试决定如何执行 → 输出应是拒绝,并带有“Execution forbidden by policy”的原因。
调用关系:它和前一个测试一起覆盖带拒绝读规则的场景。这里没有允许命令的前缀规则,所以判断会进入提权/审批路径,并验证 GranularApprovalConfig 的沙箱审批开关真正控制结果。
调用图:调用 8 个内部函数(make_session_and_context, denied_read_file_system_sandbox_policy, host_absolute_path, test_sandbox_cwd, new, from_runtime_permissions, new, try_from);外部调用 6 个(new, from_secs, new, Granular, assert_eq!, determine_action)。
intercepted_exec_policy_treats_preapproved_additional_permissions_as_default843–880 ↗
fn intercepted_exec_policy_treats_preapproved_additional_permissions_as_default()
作用:确认已经预批准的额外权限在策略评估时会被当成默认权限处理;但新的额外权限请求仍然需要提示审批。
数据流:输入是空策略、printf 程序路径、命令参数、工作区写权限,以及两种沙箱权限状态:一种经过预批准降级,一种是新请求 → 分别调用策略评估 → 输出应是预批准场景允许,新请求场景提示审批。
调用关系:它先调用 approval_sandbox_permissions 得到预批准后的权限状态,再调用 evaluate_intercepted_exec_policy 对比两个结果。它把权限状态转换和策略评估串起来检查。
调用图:调用 4 个内部函数(host_absolute_path, new, workspace_write, try_from);外部调用 3 个(assert_eq!, approval_sandbox_permissions, evaluate_intercepted_exec_policy)。
intercepted_exec_policy_rejects_disallowed_host_executable_mapping883–920 ↗
fn intercepted_exec_policy_rejects_disallowed_host_executable_mapping()
作用:确认如果策略只允许某个路径下的 git,但实际运行的是另一个路径下的 git,系统不会把它误认为命中了那条策略规则。
数据流:输入是一条 git status 审批规则、一条只允许特定 git 路径的映射、另一个实际 git 路径和命令参数 → 被测策略评估发现路径不在映射里 → 输出走启发式匹配结果,而不是策略规则驱动的结果。
调用关系:它用 host_absolute_path 造出允许路径和不允许路径,用 starlark_string 写进策略,再调用 evaluate_intercepted_exec_policy。最后通过 decision_driven_by_policy 确认这个结果不是由那条前缀策略真正驱动的。
调用图:调用 5 个内部函数(host_absolute_path, starlark_string, new, read_only, try_from);外部调用 3 个(assert!, format!, evaluate_intercepted_exec_policy)。
执行和 unified-exec 内部机制
最后一组测试端到端命令执行、MCP 工具调用分发,以及较低层级的 unified-exec 缓冲、进程和管理器内部机制。
core/src/exec_tests.rs源码 ↗
这不是正式运行时会被用户直接调用的代码,而是给开发者和持续集成系统用的测试文件。它围绕一个核心问题:程序要帮用户运行 shell 命令,但命令可能输出很多内容、被沙箱拦住、超时、被取消,或者在 Windows 上遇到复杂的读写权限。这里用很多小测试把这些边界情况固定下来。比如,它会伪造命令输出,确认“Operation not permitted”这类文字只在真正沙箱模式下才算沙箱拒绝;它会制造超大 stdout/stderr,确认保留多少字节符合规则;它还会真的启动长时间运行的进程,检查取消和超时时能不能把子进程、孙进程一起收掉。Windows 相关测试则像在检查门禁规则:哪些目录可读、可写、拒绝访问,受限令牌或提升后端能不能正确表达这些规则。没有这些测试,执行命令这种高风险功能很容易在少见但严重的情况下失控。
make_exec_output13–27 ↗
fn make_exec_output(
exit_code: i32,
stdout: &str,
stderr: &str,
aggregated: &str,
) -> ExecToolCallOutput
作用:这是测试用的小工厂函数,用来快速做出一份假的命令执行结果。测试不需要真的跑命令,也能喂给沙箱判断逻辑一份像真的输出。
数据流:进去的是退出码、标准输出、标准错误和合并输出这几段文字;它把这些文字包装成 StreamOutput,并加上一个很短的耗时和“没有超时”的标记;出来的是一份 ExecToolCallOutput 测试数据。
调用关系:多个沙箱识别测试都会先调用它造样本,再把样本交给 is_likely_sandbox_denied 去判断,这样每个测试只专注于一个场景。
调用图:调用 1 个内部函数(new);被 8 处调用(sandbox_detection_flags_sigsys_exit_code, sandbox_detection_identifies_keyword_in_stderr, sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode, sandbox_detection_ignores_network_policy_text_with_zero_exit_code, sandbox_detection_ignores_non_sandbox_mode, sandbox_detection_requires_keywords, sandbox_detection_respects_quick_reject_exit_codes, sandbox_detection_uses_aggregated_output);外部调用 1 个(from_millis)。
sandbox_detection_requires_keywords30–36 ↗
fn sandbox_detection_requires_keywords()
作用:这个测试确认:光有失败退出码还不够,不能随便说是沙箱拦截。必须看到像权限拒绝这样的关键词,才应该怀疑沙箱。
数据流:它用 make_exec_output 做出一个退出码为 1、但没有任何错误文字的结果;再交给沙箱拒绝判断;最后确认判断结果是“不是沙箱拒绝”。
调用关系:它是沙箱误判防线的一部分,和后面那些带关键词、带特殊退出码的测试一起,把判断规则的边界圈住。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
sandbox_detection_identifies_keyword_in_stderr39–42 ↗
fn sandbox_detection_identifies_keyword_in_stderr()
作用:这个测试确认:如果标准错误里出现“Operation not permitted”这种权限被拒绝的典型文字,Linux 沙箱模式下应该识别为沙箱拦截。
数据流:它造出一份失败输出,stderr 里放入权限拒绝文字;然后让沙箱判断函数检查;结果必须是“很可能被沙箱拒绝”。
调用关系:它和 sandbox_detection_requires_keywords 配成一正一反,证明判断不是只看退出码,而是真的会看错误内容。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
sandbox_detection_respects_quick_reject_exit_codes45–51 ↗
fn sandbox_detection_respects_quick_reject_exit_codes()
作用:这个测试确认:某些常见失败,比如命令不存在,不能被误当成沙箱问题。退出码 127 通常表示找不到命令。
数据流:它造出退出码 127、错误文字为 command not found 的输出;交给沙箱判断;最后确认不会被标成沙箱拒绝。
调用关系:它保护用户体验:如果命令本身写错了,系统应该报命令问题,而不是把锅甩给沙箱。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
sandbox_detection_ignores_non_sandbox_mode54–57 ↗
fn sandbox_detection_ignores_non_sandbox_mode()
作用:这个测试确认:如果根本没开沙箱,就算错误文字像权限拒绝,也不能说是沙箱拦的。
数据流:它造出带“Operation not permitted”的失败输出;但传入 SandboxType::None 表示无沙箱;结果必须是“不是沙箱拒绝”。
调用关系:它确保沙箱判断会先看当前运行模式,避免在普通执行失败时给出误导性解释。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode60–68 ↗
fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode()
作用:这个测试确认:网络策略日志文字在非沙箱模式下不能触发沙箱拒绝判断。否则普通输出里带一段策略说明也会被误报。
数据流:它构造退出码为 0、合并输出里带 CODEX_NETWORK_POLICY_DECISION 的结果;传入无沙箱模式;最后确认不会被认为是沙箱拒绝。
调用关系:它补上网络策略文字这一类特殊输出的误判风险,和非沙箱模式测试一起保证判断足够谨慎。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
sandbox_detection_uses_aggregated_output71–82 ↗
fn sandbox_detection_uses_aggregated_output()
作用:这个测试确认:沙箱判断不只看 stdout 或 stderr,也会看合并后的输出。这样错误信息被混在合并日志里时也不会漏掉。
数据流:它造出一份合并输出里含有“Read-only file system”的失败结果;交给 macOS 沙箱判断;结果应当是沙箱拒绝。
调用关系:它覆盖 aggregate 输出路径,保证执行器保存的总输出也能参与诊断。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
sandbox_detection_ignores_network_policy_text_with_zero_exit_code85–97 ↗
fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code()
作用:这个测试确认:命令成功退出时,即使输出里有网络策略文字,也不该说沙箱拒绝了。成功就是成功。
数据流:它构造退出码 0、合并输出含网络策略决定的结果;交给 Linux 沙箱判断;最后确认判断为否。
调用关系:它和网络策略相关的另一个测试一起,防止诊断逻辑看到特殊文本就过度反应。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
read_output_limits_retained_bytes_for_shell_capture100–116 ↗
async fn read_output_limits_retained_bytes_for_shell_capture()
作用:这个异步测试确认:普通 shell 捕获模式下,输出太大时只保留上限以内的字节,避免内存被撑爆。
数据流:它创建一条内存管道,后台写入超过上限的大量字节;read_output 读取时带上最大保留字节数;最后检查留下来的长度正好是上限。
调用关系:它直接检查 read_output 的限流行为,是输出保护机制的基础测试。
调用图:外部调用 4 个(assert_eq!, duplex, spawn, vec!)。
aggregate_output_prefers_stderr_on_contention119–136 ↗
fn aggregate_output_prefers_stderr_on_contention()
作用:这个测试确认:stdout 和 stderr 都很大、容量不够时,合并输出会给 stderr 更多空间。因为错误信息通常比普通输出更重要。
数据流:它准备两份都达到最大上限的输出;aggregate_output 合并时按规则分配容量;最后检查前一小段来自 stdout,剩下更多来自 stderr。
调用关系:它测试 aggregate_output 在“抢空间”场景下的优先级,是错误诊断不被普通日志挤掉的保障。
调用图:外部调用 2 个(assert_eq!, vec!)。
aggregate_output_fills_remaining_capacity_with_stderr139–156 ↗
fn aggregate_output_fills_remaining_capacity_with_stderr()
作用:这个测试确认:如果 stdout 很短,合并输出会把剩余空间尽量留给 stderr,而不是浪费容量。
数据流:它给 stdout 少量字节、stderr 大量字节;合并后检查总长度达到上限,并且 stderr 填满 stdout 后的剩余空间。
调用关系:它补充验证 aggregate_output 的容量分配不是固定死的,而是会根据 stdout 实际大小调整。
调用图:外部调用 2 个(assert_eq!, vec!)。
aggregate_output_rebalances_when_stderr_is_small159–175 ↗
fn aggregate_output_rebalances_when_stderr_is_small()
作用:这个测试确认:如果 stderr 很小,stdout 可以拿到更多空间。规则是偏向错误信息,但不会白白空着不用。
数据流:它给 stdout 一大段、stderr 只有 1 个字节;合并后 stdout 占用除最后 1 字节外的空间;最后 1 字节来自 stderr。
调用关系:它和前两个合并测试一起证明:合并逻辑会动态平衡,而不是简单粗暴地截断。
调用图:外部调用 2 个(assert_eq!, vec!)。
aggregate_output_keeps_stdout_then_stderr_when_under_cap178–195 ↗
fn aggregate_output_keeps_stdout_then_stderr_when_under_cap()
作用:这个测试确认:总输出没超过上限时,合并结果应该完整保留,并且顺序是先 stdout 后 stderr。
数据流:它准备 4 个 a 和 3 个 b;aggregate_output 合并后应得到 aaaabbb,且没有截断标记。
调用关系:它覆盖最普通、最理想的合并场景,说明截断规则只在必要时才介入。
调用图:外部调用 3 个(new, assert_eq!, vec!)。
read_output_retains_all_bytes_for_full_buffer_capture198–214 ↗
async fn read_output_retains_all_bytes_for_full_buffer_capture()
作用:这个异步测试确认:FullBuffer 模式会完整保留输出,不使用普通 shell 模式的字节上限。
数据流:它通过内存管道写入超过默认上限的大量字节;调用 read_output 时不传最大字节数;最后检查读到的长度等于实际写入长度。
调用关系:它和 read_output_limits_retained_bytes_for_shell_capture 形成对照,证明不同捕获策略真的有不同效果。
调用图:外部调用 4 个(assert_eq!, duplex, spawn, vec!)。
aggregate_output_keeps_all_bytes_when_uncapped217–238 ↗
fn aggregate_output_keeps_all_bytes_when_uncapped()
作用:这个测试确认:合并输出在没有上限时,会把 stdout 和 stderr 全部保留下来。
数据流:它准备两份各自达到默认上限大小的输出;调用 aggregate_output 时不设 max_bytes;最后检查结果长度是两者相加,内容也完整连续。
调用关系:它验证 FullBuffer 这类无限制捕获策略下,合并逻辑不会偷偷截断。
调用图:外部调用 2 个(assert_eq!, vec!)。
full_buffer_capture_policy_disables_caps_and_exec_expiration241–248 ↗
fn full_buffer_capture_policy_disables_caps_and_exec_expiration()
作用:这个测试确认 FullBuffer 捕获策略的三个核心特性:不限制保留字节、仍有 I/O 排水等待、但不使用普通执行超时。
数据流:它直接询问 ExecCapturePolicy::FullBuffer 的几个配置值;检查保留上限是 None,排水超时存在,并且 uses_expiration 为 false。
调用关系:它测试策略对象本身的承诺,后面的真实执行测试则检查这些承诺在跑命令时是否生效。
调用图:外部调用 2 个(assert!, assert_eq!)。
exec_full_buffer_capture_ignores_expiration251–292 ↗
async fn exec_full_buffer_capture_ignores_expiration() -> Result<()>
作用:这个异步测试确认:FullBuffer 模式下,即使传入很短的 expiration,命令也不会因为这个普通超时被提前杀掉。
数据流:它按平台构造一个稍等后输出 hello 的命令;用 FullBuffer 和很短 expiration 调用 exec;最后确认 stdout 是 hello,且 timed_out 为 false。
调用关系:它把 full_buffer_capture_policy_disables_caps_and_exec_expiration 的配置规则放到真实 exec 流程里验证。
调用图:调用 1 个内部函数(current_dir);外部调用 4 个(assert!, assert_eq!, vars, vec!)。
exec_full_buffer_capture_keeps_io_drain_timeout_when_descendant_holds_pipe_open296–329 ↗
async fn exec_full_buffer_capture_keeps_io_drain_timeout_when_descendant_holds_pipe_open() -> Result<()>
作用:这个 Unix 测试确认:FullBuffer 虽然忽略普通执行超时,但如果子孙进程一直占着输出管道不关,也会靠 I/O 排水保护返回。
数据流:它启动一个打印 hello 后把后台 sleep 留着的 shell;外层再用 tokio timeout 防止测试卡死;exec 应在排水保护触发后返回,且不是普通 timed_out。
调用关系:它检查 exec 在 FullBuffer 的极端管道场景下不会永久挂住,是资源回收安全网。
调用图:调用 1 个内部函数(current_dir);外部调用 5 个(from_millis, assert!, vars, timeout, vec!)。
process_exec_tool_call_preserves_full_buffer_capture_policy332–378 ↗
async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result<()>
作用:这个测试确认:更高一层的 process_exec_tool_call 不会把 FullBuffer 策略弄丢,超大输出仍能完整回来。
数据流:它构造会输出超过默认上限的命令;通过 process_exec_tool_call 执行;最后检查没有超时,stdout 长度等于原始字节数。
调用关系:它位于 exec 上层流程的测试,证明从工具调用入口到实际执行,中间不会偷偷改捕获策略。
调用图:调用 1 个内部函数(current_dir);外部调用 5 个(assert!, assert_eq!, vars, from_ref, vec!)。
windows_restricted_token_skips_external_sandbox_policies381–387 ↗
fn windows_restricted_token_skips_external_sandbox_policies()
作用:这个测试确认:Windows 受限令牌沙箱不支持外部沙箱策略。外部策略交给别的机制,不能硬套到这个后端。
数据流:它创建一个 External 权限配置;调用支持性判断;结果必须是不支持。
调用关系:它是 Windows 沙箱兼容性判断的一条边界规则,防止选择错误后端。
调用图:外部调用 1 个(assert!)。
windows_restricted_token_supports_read_only_profiles390–394 ↗
windows_proxy_enforcement_uses_elevated_backend397–410 ↗
fn windows_proxy_enforcement_uses_elevated_backend()
作用:这个测试确认:当 Windows 受限令牌还要强制代理网络规则时,需要使用提升后端;而显式 Elevated 级别也会使用提升后端。
数据流:它分别传入受限令牌加不加强制代理、以及 Elevated 级别;检查 windows_sandbox_uses_elevated_backend 的布尔结果符合预期。
调用关系:它帮助选择 Windows 沙箱实现方式,避免需要更强能力时还走普通受限令牌。
调用图:外部调用 1 个(assert!)。
windows_restricted_token_rejects_network_only_restrictions413–431 ↗
fn windows_restricted_token_rejects_network_only_restrictions()
作用:这个测试确认:只有网络受限、文件系统不受限的托管权限,Windows 受限令牌后端不能安全表达,所以必须拒绝。
数据流:它用不限制文件系统加限制网络生成权限配置;传给 unsupported_windows_restricted_token_sandbox_reason;结果应返回明确的拒绝原因。
调用关系:它检查拒绝理由函数,确保系统宁可拒绝运行,也不在无法落实限制时裸跑。
调用图:调用 3 个内部函数(from_runtime_permissions, unrestricted, current_dir);外部调用 1 个(assert_eq!)。
windows_restricted_token_rejects_managed_root_write_profiles434–461 ↗
fn windows_restricted_token_rejects_managed_root_write_profiles()
作用:这个测试确认:托管权限如果允许根目录写入,Windows 受限令牌后端不能安全执行这种规则,必须拒绝。
数据流:它构造一个根目录可写、网络受限的文件系统策略;生成权限配置;最后检查得到“无法落实,拒绝无沙箱运行”的原因文字。
调用关系:它补充 Windows 受限令牌对复杂写权限的限制,和网络-only 拒绝测试同属安全兜底。
调用图:调用 3 个内部函数(from_runtime_permissions, restricted, current_dir);外部调用 2 个(assert_eq!, vec!)。
windows_restricted_token_allows_read_only_profiles464–477 ↗
fn windows_restricted_token_allows_read_only_profiles()
作用:这个测试确认:只读配置不会被 Windows 受限令牌的“不支持原因”函数拒绝。
数据流:它传入 read_only 权限和当前目录;调用 unsupported_windows_restricted_token_sandbox_reason;结果应为 None,表示没有拒绝理由。
调用关系:它和 windows_restricted_token_supports_read_only_profiles 从两个角度证明只读模式可走这条沙箱路径。
调用图:调用 2 个内部函数(read_only, current_dir);外部调用 1 个(assert_eq!)。
windows_restricted_token_allows_workspace_write_profiles480–498 ↗
fn windows_restricted_token_allows_workspace_write_profiles()
作用:这个测试确认:工作区可写、其他地方受限的常见配置,Windows 受限令牌可以接受。
数据流:它创建 workspace_write 权限配置;传入当前目录检查不支持原因;结果为 None。
调用关系:它保证最常见的“只能改项目目录”模式不会被 Windows 后端误拒。
调用图:调用 2 个内部函数(workspace_write_with, current_dir);外部调用 1 个(assert_eq!)。
windows_elevated_allows_split_restricted_read_policies501–528 ↗
fn windows_elevated_allows_split_restricted_read_policies()
作用:这个测试确认:Windows 提升后端可以处理“只读某些指定目录”的拆分读权限规则。
数据流:它创建临时 docs 目录,把文件系统策略设为只读该目录;生成权限配置;在 Elevated 级别检查不支持原因,结果应为 None。
调用关系:它说明提升后端比普通受限令牌更能表达细分的读权限。
调用图:调用 3 个内部函数(from_runtime_permissions, restricted, from_absolute_path);外部调用 4 个(assert_eq!, create_dir_all, new, vec!)。
windows_restricted_token_rejects_split_only_filesystem_policies531–569 ↗
fn windows_restricted_token_rejects_split_only_filesystem_policies()
作用:这个测试确认:普通 Windows 受限令牌不能直接表达“项目根可写,但另一个目录只读”这种拆分文件系统规则。
数据流:它构造项目根可写、docs 只读的策略;传给不支持原因函数;结果应返回“不能直接落实拆分读限制”的错误文字。
调用关系:它保护普通受限令牌路径,遇到它做不到的细粒度规则时不假装能做。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 4 个(assert_eq!, create_dir_all, new, vec!)。
windows_restricted_token_rejects_root_write_read_only_carveouts572–608 ↗
fn windows_restricted_token_rejects_root_write_read_only_carveouts()
作用:这个测试确认:如果规则是根目录可写,但某个子目录只读,普通受限令牌也不能安全表达。
数据流:它构造根可写加 docs 只读的策略;调用不支持原因函数;结果应是“不能落实拆分可写根集合”的拒绝原因。
调用关系:它覆盖另一种容易误判的拆分写权限,避免只读例外被忽略。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 4 个(assert_eq!, create_dir_all, new, vec!)。
windows_restricted_token_supports_full_read_split_write_read_carveouts611–663 ↗
fn windows_restricted_token_supports_full_read_split_write_read_carveouts()
作用:这个测试确认:一种更可投影的规则可以被普通受限令牌转换成覆盖项:全盘可读、项目可写,但某个目录额外禁止写。
数据流:它构造根可读、项目根可写、docs 只读的策略;调用 resolve_windows_restricted_token_filesystem_overrides;结果应返回额外 deny-write 的 docs 路径。
调用关系:它测试权限转换器能把某些复杂规则翻译成 Windows 沙箱覆盖项,而不是一概拒绝。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 5 个(assert_eq!, canonicalize, create_dir_all, new, vec!)。
windows_restricted_token_rejects_unreadable_split_carveouts666–710 ↗
fn windows_restricted_token_rejects_unreadable_split_carveouts()
作用:这个测试确认:普通受限令牌不能直接表达“某个路径完全不可读”的拆分拒绝规则。
数据流:它构造根可读、项目可写、blocked 路径 Deny 的策略;解析覆盖项时应返回错误,说明不能落实 deny-read。
调用关系:它和前一个可支持场景形成对照,说明普通受限令牌只支持部分覆盖,不支持所有拒绝规则。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 5 个(assert_eq!, canonicalize, create_dir_all, new, vec!)。
windows_elevated_supports_split_restricted_read_roots713–747 ↗
fn windows_elevated_supports_split_restricted_read_roots()
作用:这个测试确认:Windows 提升后端可以把“只读某个目录”转换成明确的 read_roots_override。
数据流:它创建 docs 目录并规范化路径;构造只读 docs 的策略;调用 resolve_windows_elevated_filesystem_overrides;结果应包含 docs 作为唯一读根。
调用关系:它测试提升后端的文件系统覆盖项生成能力,和普通受限令牌的限制形成对比。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 5 个(assert_eq!, canonicalize, create_dir_all, new, vec!)。
windows_elevated_supports_split_write_read_carveouts750–801 ↗
fn windows_elevated_supports_split_write_read_carveouts()
作用:这个测试确认:Windows 提升后端能处理“项目可写,但其中某个目录只读”的例外。
数据流:它构造根可读、项目根可写、docs 只读的策略;解析提升后端覆盖项;结果应把 docs 放进额外禁止写入路径。
调用关系:它证明提升后端可以用 deny-write 覆盖项表达普通受限令牌难以表达的只读 carveout。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 5 个(assert_eq!, canonicalize, create_dir_all, new, vec!)。
windows_elevated_supports_unreadable_split_carveouts804–860 ↗
fn windows_elevated_supports_unreadable_split_carveouts()
作用:这个测试确认:Windows 提升后端能处理某个子路径完全不可读不可写的例外。
数据流:它构造 blocked 路径 Deny 的策略;解析提升后端覆盖项;结果应同时把 blocked 放入 deny-read 和 deny-write。
调用关系:它覆盖更强的拒绝规则,说明提升后端能表达细粒度隔离。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 5 个(assert_eq!, canonicalize, create_dir_all, new, vec!)。
windows_elevated_supports_unreadable_globs863–913 ↗
fn windows_elevated_supports_unreadable_globs()
作用:这个测试确认:Windows 提升后端能把通配符规则,比如 **/*.env,展开成实际要禁止读取的文件路径。
数据流:它在临时目录创建 app/.env 文件;构造匹配 .env 的 Deny 规则;解析覆盖项;结果应把这个 secret 文件加入额外 deny-read。
调用关系:它测试 glob pattern(通配符匹配规则)到实际路径的转换,避免敏感文件因模式规则没展开而泄漏。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 5 个(assert_eq!, create_dir_all, write, new, vec!)。
windows_elevated_rejects_reopened_writable_descendants916–968 ↗
fn windows_elevated_rejects_reopened_writable_descendants()
作用:这个测试确认:Windows 提升后端不能接受“父目录只读,但子目录又重新开放写入”的规则,因为这会让权限边界变得危险。
数据流:它构造 docs 只读、docs/nested 又可写的策略;调用不支持原因函数;结果应返回不能重新打开只读例外下可写子目录的错误。
调用关系:它是提升后端的安全刹车,防止复杂嵌套规则被错误翻译。
调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 4 个(assert_eq!, create_dir_all, new, vec!)。
process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions971–984 ↗
fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions()
作用:这个测试确认:当只有网络受限、文件系统不受限时,工具执行仍会选择平台提供的沙箱,而不是直接无保护运行。
数据流:它先询问当前平台默认沙箱类型;再用 unrestricted 文件系统加 restricted 网络调用选择函数;最后检查选择结果等于平台沙箱或 None。
调用关系:它检查 process_exec_tool_call 前的沙箱选择逻辑,确保网络限制也能触发合适的沙箱路径。
调用图:外部调用 2 个(assert_eq!, get_platform_sandbox)。
build_exec_request_preserves_windows_workspace_roots987–1019 ↗
fn build_exec_request_preserves_windows_workspace_roots() -> Result<()>
作用:这个测试确认:构建执行请求时,Windows 工作区根目录列表会原样保留下来。沙箱需要这些根目录来决定哪里是项目范围。
数据流:它创建临时 cwd 和额外 root;调用 build_exec_request;最后检查请求里的 windows_sandbox_workspace_roots 与输入列表完全一致。
调用关系:它测试从 ExecParams 到底层执行请求的打包过程,避免 Windows 沙箱因为丢失工作区根而放错权限。
调用图:外部调用 4 个(new, assert_eq!, new, vec!)。
sandbox_detection_flags_sigsys_exit_code1023–1027 ↗
fn sandbox_detection_flags_sigsys_exit_code()
作用:这个 Unix 测试确认:进程因为 SIGSYS 信号退出时,会被识别为可能被 seccomp 沙箱拦截。SIGSYS 可以理解为系统调用被安全规则打断。
数据流:它把信号编号换算成退出码,造出没有文字的执行结果;交给 LinuxSeccomp 沙箱判断;结果应为沙箱拒绝。
调用关系:它补充了不依赖错误文字的识别路径,因为有些沙箱拦截只留下特殊退出码。
调用图:调用 1 个内部函数(make_exec_output);外部调用 1 个(assert!)。
kill_child_process_group_kills_grandchildren_on_timeout1031–1094 ↗
async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()>
作用:这个 Unix 异步测试确认:命令超时时,不只杀掉直接子进程,也会杀掉它启动的孙进程。否则后台进程会变成“野孩子”继续跑。
数据流:它启动 shell,让 shell 开一个长睡眠后台进程并打印 pid;exec 因超时结束后,测试反复用 kill(pid, 0) 检查那个孙进程是否还活着;最后要求它已经消失。
调用关系:它直接验证 exec 的进程组清理能力,是超时处理里非常重要的安全测试。
调用图:调用 1 个内部函数(current_dir);外部调用 7 个(from_millis, assert!, last_os_error, kill, vars, sleep, vec!)。
process_exec_tool_call_respects_cancellation_token1097–1139 ↗
async fn process_exec_tool_call_respects_cancellation_token() -> Result<()>
作用:这个异步测试确认:外部取消信号能让正在运行的工具命令尽快停下,而且这不算普通超时。
数据流:它用 long_running_command 构造长时间命令;创建 CancellationToken(取消令牌,可以理解为远程停止按钮);后台一秒后按下取消;process_exec_tool_call 应在 5 秒内返回,且 timed_out 为 false、退出码不是超时码。
调用关系:它测试高层工具执行流程对取消令牌的响应,long_running_command 提供跨平台长命令。
调用图:调用 2 个内部函数(long_running_command, current_dir);外部调用 11 个(new, from_millis, from_secs, assert!, assert_ne!, Cancellation, vars, from_ref, spawn, sleep (+1 more))。
process_exec_tool_call_cancellation_allows_sigterm_cleanup1143–1249 ↗
async fn process_exec_tool_call_cancellation_allows_sigterm_cleanup() -> Result<()>
作用:这个 Unix 测试确认:取消命令时会先给进程机会收到 SIGTERM 做清理,然后再处理不肯退出的后代进程。SIGTERM 可以理解为礼貌地请进程退出。
数据流:它启动一个 shell:父进程收到 TERM 会写 cleanup 标记,另有一个忽略 TERM 的子进程;测试等 ready 标记出现后取消;最后检查 cleanup 文件存在,同时确认那个忽略 TERM 的后代也被强制清掉。
调用关系:它比普通取消测试更细,验证 process_exec_tool_call 的取消流程既温和地允许清理,又不会留下顽固子进程。
调用图:调用 1 个内部函数(current_dir);外部调用 15 个(new, from_millis, from_secs, assert!, assert_eq!, last_os_error, kill, vars, read_to_string, from_ref (+5 more))。
long_running_command1261–1269 ↗
fn long_running_command() -> Vec<String>
作用:这是测试用的跨平台长时间命令生成器,用来制造一个不会马上结束的进程。取消测试需要这种“靶子”。
数据流:在 Unix 上它输出 /bin/sh 执行 sleep 30 的命令;在 Windows 上它输出 powershell 执行 Start-Sleep 30 的命令;结果是一组字符串形式的命令参数。
调用关系:它被 process_exec_tool_call_respects_cancellation_token 调用,让同一个取消测试在不同操作系统上都能运行。
调用图:被 1 处调用(process_exec_tool_call_respects_cancellation_token);外部调用 1 个(vec!)。
core/src/mcp_tool_call_tests.rs源码 ↗
MCP 可以简单理解成“让模型调用外部工具的一套接口”。这个文件不实现正式功能,而是用很多测试把关键规则钉牢:哪些工具调用要先问用户,哪些只读工具可以跳过;用户点“本次允许”或“以后都允许”后,配置文件该怎么写;工具返回图片、大文本、鉴权失败时系统该怎么处理;追踪日志里是否带上会话、轮次、工具名等排查问题必需的信息。它还测试插件里的 MCP 配置、Codex Apps 连接器、Guardian 自动审核、PermissionRequest 钩子等场景。可以把它看成机场安检演练:不是飞机本身,但反复模拟危险品、通行证、人工复核和记录留痕,保证真正运行时不会放错行或漏登记。
annotations55–67 ↗
fn annotations(
read_only: Option<bool>,
destructive: Option<bool>,
open_world: Option<bool>,
) -> ToolAnnotations
作用:快速造一份工具安全标记,用来说明工具是不是只读、是不是有破坏性、是不是会接触外部世界。测试里反复用它来模拟不同风险的 MCP 工具。
数据流:输入几个可选的真假值 → 交给 ToolAnnotations::from_raw 组装成标准标记 → 输出一份可被审批逻辑读取的注解对象,不改动外部状态。
调用关系:很多审批相关测试先用它造测试材料,再把材料交给 requires_mcp_tool_approval、maybe_request_mcp_tool_approval 或 Guardian 审核请求构造逻辑。
调用图:被 13 处调用(approval_not_required_when_read_only_and_other_hints_are_absent, approval_required_when_destructive_even_if_read_only_true, approval_required_when_read_only_false_and_destructive, approval_required_when_read_only_false_and_open_world, approve_mode_skips_guardian_in_every_permission_mode, approve_mode_skips_when_annotations_do_not_require_approval, full_access_mode_skips_mcp_tool_approval_for_all_approval_modes, guardian_mcp_review_request_includes_annotations_when_present, guardian_mode_mcp_denial_returns_rationale_message, guardian_mode_skips_auto_when_annotations_do_not_require_approval (+3 more));外部调用 1 个(from_raw)。
approval_metadata69–88 ↗
fn approval_metadata(
connector_id: Option<&str>,
connector_name: Option<&str>,
connector_description: Option<&str>,
tool_title: Option<&str>,
tool_description: Option<&str>,
) ->
作用:快速造一份 MCP 工具审批元数据,也就是审批弹窗或审核请求里要展示的连接器名、工具名、说明等信息。
数据流:输入连接器和工具的几个可选文字 → 填进 McpToolApprovalMetadata → 输出一份默认字段齐全、指定字段有值的测试元数据。
调用关系:各类审批弹窗、持久化审批、Guardian 审核、插件元数据测试都会先调用它准备统一的假数据。
调用图:被 5 处调用(approval_elicitation_request_uses_message_override_and_preserves_tool_params_keys, codex_apps_auth_failure_metadata, codex_apps_connectors_support_persistent_approval, guardian_mcp_review_request_includes_invocation_metadata, plugin_mcp_tool_call_request_meta_includes_plugin_id)。
mcp_turn_metadata_context90–95 ↗
fn mcp_turn_metadata_context(turn_context: &TurnContext) -> McpTurnMetadataContext<'_>
作用:从当前对话轮次里取出要随 MCP 请求带过去的模型信息。这样外部工具能知道这次调用发生在哪种模型和推理设置下。
数据流:输入 TurnContext → 读取模型代号和推理强度 → 输出 McpTurnMetadataContext 这块小上下文,不写任何状态。
调用关系:构造 MCP 请求 meta 的测试用它计算期望值,再和 build_mcp_tool_call_request_meta 的真实结果对比。
调用图:调用 1 个内部函数(effective_reasoning_effort);被 4 处调用(codex_apps_tool_call_request_meta_includes_call_id_without_existing_codex_apps_meta, codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps_meta, mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server, plugin_mcp_tool_call_request_meta_includes_plugin_id)。
write_sample_plugin_mcp97–119 ↗
fn write_sample_plugin_mcp(codex_home: &std::path::Path)
作用:在临时目录里写一个假的插件和它的 MCP 配置。这样测试不用依赖真实安装的插件。
数据流:输入 codex_home 路径 → 创建插件目录、写 plugin.json 和 .mcp.json → 磁盘上出现一个名叫 sample 的测试插件。
调用关系:插件审批策略和插件审批持久化测试先调用它布置现场,再加载配置检查系统是否读到了插件里的 MCP 服务。
调用图:被 3 处调用(custom_mcp_tool_approval_mode_uses_plugin_mcp_policy, custom_mcp_tool_approval_mode_uses_updated_plugin_mcp_policy_after_cache_warm, maybe_persist_mcp_tool_approval_writes_plugin_mcp_policy);外部调用 3 个(join, create_dir_all, write)。
prompt_options121–129 ↗
fn prompt_options(
allow_session_remember: bool,
allow_persistent_approval: bool,
) -> McpToolApprovalPromptOptions
作用:快速生成审批提示框的选项开关,说明是否允许“本次会话记住”和“永久记住”。
数据流:输入两个布尔值 → 填入 McpToolApprovalPromptOptions → 输出给问题构造函数使用。
调用关系:多项审批问题文案测试用它控制按钮数量,验证 build_mcp_tool_approval_question 是否按规则显示选项。
调用图:被 5 处调用(approval_elicitation_request_uses_message_override_and_preserves_tool_params_keys, codex_apps_tool_question_uses_fallback_app_label, custom_mcp_tool_question_mentions_server_name, custom_mcp_tool_question_offers_session_remember_and_always_allow, trusted_codex_apps_tool_question_offers_always_allow)。
execute_mcp_tool_call_records_replayable_correlation132–183 ↗
async fn execute_mcp_tool_call_records_replayable_correlation() -> anyhow::Result<()>
作用:测试真实 MCP 调用路径即使调用失败,也会写出可回放追踪里的关联 ID。这个 ID 像快递单号,方便以后把工具调用和回放记录对上。
数据流:先建临时会话和追踪包 → 发起一个不存在后端的 MCP 调用 → 读取回放包,确认 call_id 里有 MCP 关联信息。
调用关系:它调用 attach_trace_bundle 接上追踪系统,最后用 single_bundle_dir 找到产物,并验证 execute_mcp_tool_call 的留痕行为。
调用图:调用 3 个内部函数(attach_trace_bundle, single_bundle_dir, make_session_and_context);外部调用 4 个(assert!, replay_bundle, json!, tempdir)。
install_mcp_permission_request_hook185–272 ↗
fn install_mcp_permission_request_hook(
session: &mut Session,
turn_context: &TurnContext,
matcher: &str,
hook_output: &serde_json::Value,
) -> std::path::PathBuf
作用:给测试会话安装一个 PermissionRequest 钩子。钩子可以理解成外部脚本门卫,工具调用前先问它放不放行。
数据流:输入会话、轮次、匹配规则和脚本输出 → 在临时 home 写 Python 脚本和 hooks.json → 把新的 Hooks 对象装进 session,并返回日志文件路径。
调用关系:权限钩子相关测试用它搭好门卫,再调用 maybe_request_mcp_tool_approval,看钩子是否收到正确输入、是否能允许或被跳过。
调用图:调用 2 个内部函数(trusted_config_layer_stack, new);被 3 处调用(permission_request_hook_allows_mcp_tool_call, permission_request_hook_runs_after_remembered_mcp_approval, permission_request_hook_uses_hook_tool_name_without_metadata);外部调用 12 个(new, to_string, new, assert_eq!, cfg!, list_hooks, format!, default, json!, create_dir_all (+2 more))。
attach_trace_bundle275–301 ↗
fn attach_trace_bundle(
session: &mut Session,
turn_context: &TurnContext,
root: &Path,
) -> anyhow::Result<()>
作用:给一个测试会话挂上可回放的 rollout trace,也就是一份能记录工具调用过程的追踪档案。
数据流:输入会话、轮次和根目录 → 创建测试用 ThreadTraceContext 并记录轮次开始 → 更新 session.services.rollout_thread_trace。
调用关系:execute_mcp_tool_call_records_replayable_correlation 先用它打开追踪记录,再执行 MCP 调用检查是否写入关联信息。
调用图:调用 1 个内部函数(start_root_in_root_for_test);被 1 处调用(execute_mcp_tool_call_records_replayable_correlation);外部调用 1 个(from)。
single_bundle_dir304–311 ↗
fn single_bundle_dir(root: &Path) -> anyhow::Result<PathBuf>
作用:从临时追踪根目录里找出唯一生成的回放包目录。它顺便确认测试没有多生成或少生成包。
数据流:输入根目录 → 读取并排序子目录 → 要求只有一个条目,然后返回这个路径。
调用关系:追踪回放测试用它定位刚生成的包,再交给 replay_bundle 读取内容。
调用图:被 1 处调用(execute_mcp_tool_call_records_replayable_correlation);外部调用 2 个(assert_eq!, read_dir)。
mcp_app_resource_uri_reads_known_tool_meta_keys314–340 ↗
fn mcp_app_resource_uri_reads_known_tool_meta_keys()
作用:测试系统能从几种已知写法里读出 MCP 应用界面资源地址。这个地址通常指向工具配套的 UI 页面。
数据流:构造嵌套、扁平和 outputTemplate 三种 JSON meta → 调用 get_mcp_app_resource_uri → 断言都能得到正确 URI。
调用关系:它直接覆盖资源 URI 解析函数,保证不同服务返回的兼容格式都被支持。
调用图:外部调用 2 个(assert_eq!, json!)。
openai_file_params_are_only_honored_for_codex_apps343–357 ↗
fn openai_file_params_are_only_honored_for_codex_apps()
作用:测试 openai/fileParams 这种文件输入声明只对 Codex Apps 服务生效。这样普通 MCP 服务不能随便启用特殊文件参数能力。
数据流:输入带 fileParams 的 meta → 分别用 Codex Apps 服务名和普通服务名解析 → 前者返回参数名,后者返回空。
调用关系:它保护 openai_file_input_params_for_server 的边界,避免非官方应用服务借用 Codex Apps 专属行为。
调用图:外部调用 2 个(assert_eq!, json!)。
approval_required_when_read_only_false_and_destructive360–363 ↗
fn approval_required_when_read_only_false_and_destructive()
作用:测试工具标记为非只读且有破坏性时,必须要求审批。
数据流:用 annotations 造风险标记 → 调用 requires_mcp_tool_approval → 期望结果为 true。
调用关系:这是审批规则矩阵中的一个危险场景,直接验证核心判断函数。
调用图:调用 1 个内部函数(annotations);外部调用 1 个(assert_eq!)。
approval_required_when_read_only_false_and_open_world366–369 ↗
fn approval_required_when_read_only_false_and_open_world()
作用:测试工具非只读并且会接触外部世界时,必须要求审批。
数据流:造出 read_only=false、open_world=true 的注解 → 送入审批判断 → 得到需要审批。
调用关系:它和其他审批规则测试一起覆盖 requires_mcp_tool_approval 的不同风险组合。
调用图:调用 1 个内部函数(annotations);外部调用 1 个(assert_eq!)。
approval_required_when_destructive_even_if_read_only_true372–375 ↗
fn approval_required_when_destructive_even_if_read_only_true()
作用:测试只要标了破坏性,即使同时说自己只读,也仍然要审批。系统优先相信更危险的信号。
数据流:输入互相矛盾的注解 → 调用审批判断 → 输出 true。
调用关系:它防止工具用 read_only 标记掩盖 destructive 标记。
调用图:调用 1 个内部函数(annotations);外部调用 1 个(assert_eq!)。
approval_required_when_annotations_are_absent378–380 ↗
fn approval_required_when_annotations_are_absent()
作用:测试没有任何安全注解时默认要审批。未知风险按有风险处理。
数据流:输入 None → 调用 requires_mcp_tool_approval → 得到 true。
调用关系:这是审批逻辑的保守兜底测试,避免服务缺少元数据时自动放行。
调用图:外部调用 1 个(assert_eq!)。
approval_not_required_when_read_only_and_other_hints_are_absent383–390 ↗
fn approval_not_required_when_read_only_and_other_hints_are_absent()
作用:测试明确只读且没有破坏性、外部世界提示时,可以不要求审批。
数据流:造出 read_only=true 的安全注解 → 调用审批判断 → 得到 false。
调用关系:它验证安全工具不会被不必要的弹窗打断,是危险场景测试的反面。
调用图:调用 1 个内部函数(annotations);外部调用 1 个(assert_eq!)。
prompt_mode_does_not_allow_persistent_remember393–408 ↗
fn prompt_mode_does_not_allow_persistent_remember()
作用:测试 Prompt 模式下,即使用户选择“记住”,也会被降级为普通接受。这个模式只允许单次明确确认。
数据流:输入 AcceptForSession 和 AcceptAndRemember 两种决定 → 经过 normalize_approval_decision_for_mode → 都变成 Accept。
调用关系:它约束审批模式归一化逻辑,防止 Prompt 模式偷偷写入长期许可。
调用图:外部调用 1 个(assert_eq!)。
mcp_tool_call_span_records_expected_fields411–459 ↗
async fn mcp_tool_call_span_records_expected_fields()
作用:测试 MCP 工具调用的追踪 span 里有关键字段。span 可以理解成日志里的一个时间段记录。
数据流:设置临时 tracing 输出 → 创建会话并包一层 mcp_tool_call_span → 读取日志字符串,检查服务名、工具名、端口、会话 ID 等字段。
调用关系:它验证可观测性代码,让排查线上 MCP 调用问题时不会缺少基本线索。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 9 个(leak, new, new, from_utf8, new, assert!, new, set_default, fmt)。
mcp_result_telemetry_span_logs461–503 ↗
async fn mcp_result_telemetry_span_logs(meta: Option<serde_json::Value>) -> String
作用:辅助函数:执行一次带结果 meta 的 MCP span,并返回生成的日志文本。
数据流:输入可选的结果 meta → 建会话、建 CallToolResult、在 span 中记录结果遥测 → 输出日志字符串。
调用关系:三个结果遥测测试复用它,分别检查白名单字段、无效字段和超长字段的处理。
调用图:调用 1 个内部函数(make_session_and_context);被 3 处调用(mcp_result_telemetry_ignores_invalid_and_missing_values, mcp_result_telemetry_records_allowlisted_span_fields, mcp_result_telemetry_truncates_long_target_id);外部调用 9 个(leak, new, new, current, from_utf8, new, new, set_default, fmt)。
mcp_result_telemetry_records_allowlisted_span_fields506–528 ↗
async fn mcp_result_telemetry_records_allowlisted_span_fields()
作用:测试工具结果里的遥测信息只把允许的字段写进 span。未知字段不能随便进日志。
数据流:输入含 target_id、server_user_flow 和额外字段的 meta → 生成日志 → 确认前两个出现、额外字段不出现。
调用关系:它依赖 mcp_result_telemetry_span_logs,覆盖 record_mcp_result_span_telemetry 的字段白名单。
调用图:调用 1 个内部函数(mcp_result_telemetry_span_logs);外部调用 2 个(assert!, json!)。
mcp_result_telemetry_ignores_invalid_and_missing_values531–563 ↗
async fn mcp_result_telemetry_ignores_invalid_and_missing_values()
作用:测试遥测字段类型不对或缺失时会被忽略,而不是写出乱数据。
数据流:分别输入类型错误、结构缺失和无 meta 三种结果 → 生成日志 → 确认目标字段都没有出现。
调用关系:它和白名单测试一起保证结果遥测既有用又干净。
调用图:调用 1 个内部函数(mcp_result_telemetry_span_logs);外部调用 2 个(assert!, json!)。
mcp_result_telemetry_truncates_long_target_id566–582 ↗
async fn mcp_result_telemetry_truncates_long_target_id()
作用:测试超长 target_id 会被截断,避免日志字段过大。
数据流:构造超过上限的字符串 → 写入遥测 meta 并生成日志 → 确认只保留前半截,尾巴不出现。
调用关系:它验证 record_mcp_result_span_telemetry 使用的截断规则,和字符边界测试相互补充。
调用图:调用 1 个内部函数(mcp_result_telemetry_span_logs);外部调用 3 个(assert!, format!, json!)。
truncates_strings_on_char_boundaries585–595 ↗
fn truncates_strings_on_char_boundaries()
作用:测试截断字符串时不会把多字节字符切坏。比如带重音的字符不能只切一半。
数据流:输入由 á 组成的长字符串和普通短字符串 → 调用 truncate_str_to_char_boundary → 确认长串按字符截断、短串原样返回。
调用关系:它为遥测 target_id 截断提供底层安全保证。
调用图:外部调用 2 个(assert_eq!, format!)。
approval_elicitation_request_uses_message_override_and_preserves_tool_params_keys598–693 ↗
async fn approval_elicitation_request_uses_message_override_and_preserves_tool_params_keys()
作用:测试构造 MCP 审批询问请求时,会使用自定义提示文案,并保留工具参数的原始键名。
数据流:输入会话、元数据、工具参数、展示参数和覆盖文案 → 构造 elicitation 请求 → 和完整期望结构逐项对比。
调用关系:它覆盖 build_mcp_tool_approval_question 与 build_mcp_tool_approval_elicitation_request 的组合效果。
调用图:调用 3 个内部函数(approval_metadata, prompt_options, make_session_and_context);外部调用 2 个(assert_eq!, json!)。
custom_mcp_tool_question_mentions_server_name696–721 ↗
fn custom_mcp_tool_question_mentions_server_name()
作用:测试普通自定义 MCP 服务的审批问题会明确提到服务名。用户能看懂是谁要调用工具。
数据流:输入 custom_server 和工具名 → 构造问题 → 检查标题、正文,并确认没有长期记住选项。
调用关系:它验证 build_mcp_tool_approval_question 在非 Codex Apps 场景下的文案。
调用图:调用 1 个内部函数(prompt_options);外部调用 2 个(assert!, assert_eq!)。
codex_apps_tool_question_uses_fallback_app_label724–740 ↗
fn codex_apps_tool_question_uses_fallback_app_label()
作用:测试 Codex Apps 没有连接器名字时,审批文案用“this app”兜底。
数据流:输入 Codex Apps 服务名、无 connector_name → 构造问题 → 断言问题文字使用通用应用称呼。
调用关系:它覆盖应用工具审批问题的缺省命名行为。
调用图:调用 1 个内部函数(prompt_options);外部调用 1 个(assert_eq!)。
trusted_codex_apps_tool_question_offers_always_allow743–776 ↗
fn trusted_codex_apps_tool_question_offers_always_allow()
作用:测试可信 Codex Apps 工具可以显示“本次会话记住”和“以后都允许”按钮。
数据流:输入连接器名和两个记住开关 → 构造问题 → 检查按钮顺序和说明文字。
调用关系:它验证审批 UI 选项生成,特别是持久许可按钮是否在允许时出现。
调用图:调用 1 个内部函数(prompt_options);外部调用 2 个(assert!, assert_eq!)。
codex_apps_tool_question_without_elicitation_omits_always_allow779–812 ↗
fn codex_apps_tool_question_without_elicitation_omits_always_allow()
作用:测试当 MCP elicitation 能力关闭时,Codex Apps 审批问题不显示“以后都允许”。
数据流:构造 session 和 persistent key,但关闭 tool_call_mcp_elicitation_enabled → 生成提示选项 → 确认只剩接受、本会话接受、取消。
调用关系:它覆盖 mcp_tool_approval_prompt_options 和问题构造之间的联动。
调用图:外部调用 1 个(assert_eq!)。
custom_mcp_tool_question_offers_session_remember_and_always_allow815–841 ↗
fn custom_mcp_tool_question_offers_session_remember_and_always_allow()
作用:测试自定义 MCP 服务在允许时也能提供会话记住和永久记住选项。
数据流:输入自定义服务、工具名和两个允许开关 → 构造问题 → 检查四个按钮完整出现。
调用关系:它和 Codex Apps 的按钮测试一起确认不同服务类型的审批体验一致。
调用图:调用 1 个内部函数(prompt_options);外部调用 1 个(assert_eq!)。
custom_servers_support_session_and_persistent_approval844–868 ↗
fn custom_servers_support_session_and_persistent_approval()
作用:测试普通自定义 MCP 服务可以生成会话级和永久级审批记忆键。
数据流:输入一个 custom_server 调用 → 分别计算 session key 和 persistent key → 二者都等于预期的 server/tool 组合。
调用关系:它验证 session_mcp_tool_approval_key 与 persistent_mcp_tool_approval_key 对自定义服务的支持。
调用图:外部调用 1 个(assert_eq!)。
codex_apps_connectors_support_persistent_approval871–898 ↗
fn codex_apps_connectors_support_persistent_approval()
作用:测试 Codex Apps 连接器也支持记住审批,并且记忆键里包含 connector_id。
数据流:输入 Codex Apps 调用和 calendar 元数据 → 计算两类审批 key → 输出带 server、connector_id、tool_name 的预期 key。
调用关系:它保证连接器级工具许可不会和别的连接器混在一起。
调用图:调用 1 个内部函数(approval_metadata);外部调用 1 个(assert_eq!)。
sanitize_mcp_tool_result_for_model_rewrites_image_content901–935 ↗
fn sanitize_mcp_tool_result_for_model_rewrites_image_content()
作用:测试当模型不支持图片输入时,MCP 工具返回的图片会被替换成文字提示。
数据流:输入含 image 和 text 的成功结果,并声明不支持图片 → 调用 sanitize_mcp_tool_result_for_model → 输出里图片变成“已省略”的文本。
调用关系:它覆盖模型可见结果清洗逻辑,防止不支持图片的模型收到无法处理的数据。
调用图:外部调用 2 个(assert_eq!, vec!)。
sanitize_mcp_tool_result_for_model_preserves_image_when_supported938–957 ↗
fn sanitize_mcp_tool_result_for_model_preserves_image_when_supported()
作用:测试模型支持图片输入时,图片结果会原样保留。
数据流:输入含图片、结构化内容、错误标志和 meta 的结果 → 声明支持图片 → 输出等于原始结果。
调用关系:它是图片清洗测试的反面,防止系统过度删除有效内容。
调用图:外部调用 3 个(assert_eq!, json!, vec!)。
truncate_mcp_tool_result_for_event_preserves_small_result960–975 ↗
fn truncate_mcp_tool_result_for_event_preserves_small_result()
作用:测试小的 MCP 工具结果不会被截断。正常结果应完整发到事件流。
数据流:输入一份很小的成功结果 → 调用 truncate_mcp_tool_result_for_event → 输出和原始结果一致。
调用关系:它验证事件展示截断逻辑不会误伤普通结果。
调用图:外部调用 3 个(assert_eq!, json!, vec!)。
truncate_mcp_tool_result_for_event_bounds_large_result978–1012 ↗
fn truncate_mcp_tool_result_for_event_bounds_large_result()
作用:测试超大的成功结果会被压缩到安全大小,避免事件或界面被巨量文本撑爆。
数据流:输入包含巨大文本、结构化内容和 meta 的结果 → 截断 → 输出只保留带截断提示的文本,结构化内容和 meta 被去掉。
调用关系:它覆盖 MCP 工具结果发给前端事件前的大小保护。
调用图:外部调用 5 个(assert!, assert_eq!, json!, to_string, vec!)。
truncate_mcp_tool_result_for_event_bounds_large_error1015–1023 ↗
fn truncate_mcp_tool_result_for_event_bounds_large_error()
作用:测试超长错误消息也会被截断,但仍然保持错误状态。
数据流:输入巨大错误字符串 → 调用事件截断函数 → 输出较短错误文本,并包含 truncated 提示。
调用关系:它和大成功结果测试一起保护事件通道不被异常内容拖垮。
调用图:外部调用 1 个(assert!)。
mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server1026–1066 ↗
async fn mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server()
作用:测试自定义 MCP 服务调用时,请求 meta 会带上当前轮次的模型信息。
数据流:创建会话 → 计算期望 turn metadata → 构造请求 meta → 检查 model 和 reasoning_effort 等字段一致。
调用关系:它验证 build_mcp_tool_call_request_meta 给普通 MCP 服务加上下文信息。
调用图:调用 2 个内部函数(mcp_turn_metadata_context, make_session_and_context);外部调用 1 个(assert_eq!)。
mcp_tool_call_request_meta_includes_turn_started_at_unix_ms1069–1092 ↗
async fn mcp_tool_call_request_meta_includes_turn_started_at_unix_ms()
作用:测试请求 meta 能带上本轮开始时间的毫秒时间戳。
数据流:设置 turn_started_at_unix_ms → 构造 MCP 请求 meta → 从 meta 中读回并比较该时间戳。
调用关系:它覆盖 turn_metadata_state 到 MCP 请求 meta 的时间字段传递。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 1 个(assert_eq!)。
plugin_mcp_tool_call_request_meta_includes_plugin_id1095–1115 ↗
async fn plugin_mcp_tool_call_request_meta_includes_plugin_id()
作用:测试插件提供的 MCP 工具调用会在请求 meta 中带上 plugin_id。
数据流:创建元数据并填入 plugin_id → 构造请求 meta → 输出同时包含轮次信息和插件 ID。
调用关系:它确保插件来源能随请求传到 MCP 服务,方便识别调用来自哪个插件。
调用图:调用 3 个内部函数(approval_metadata, mcp_turn_metadata_context, make_session_and_context);外部调用 1 个(assert_eq!)。
mcp_tool_call_item_includes_plugin_id1118–1149 ↗
async fn mcp_tool_call_item_includes_plugin_id()
作用:测试发给客户端的 MCP 工具调用开始事件里包含 plugin_id。
数据流:创建带事件接收器的会话 → 调用 notify_mcp_tool_call_started → 从事件流取出 ItemStarted 并检查 item.plugin_id。
调用关系:它覆盖用户界面或协议事件中的插件来源展示。
调用图:调用 1 个内部函数(make_session_and_context_with_rx);外部调用 4 个(assert_eq!, panic!, from_secs, timeout)。
codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps_meta1152–1197 ↗
async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps_meta()
作用:测试 Codex Apps 工具调用的请求 meta 同时带轮次信息和应用专属 meta。
数据流:输入连接器和 _codex_apps 元数据 → 构造请求 meta → 输出里包含 turn metadata、call_id 和原有应用资源字段。
调用关系:它验证 Codex Apps 专用请求封装不会丢掉已有应用元数据。
调用图:调用 2 个内部函数(mcp_turn_metadata_context, make_session_and_context);外部调用 2 个(assert_eq!, json!)。
codex_apps_tool_call_request_meta_includes_call_id_without_existing_codex_apps_meta1200–1221 ↗
async fn codex_apps_tool_call_request_meta_includes_call_id_without_existing_codex_apps_meta()
作用:测试即使没有现成 Codex Apps meta,也会至少把 call_id 放进去。
数据流:输入 Codex Apps 服务名、call_id、无元数据 → 构造请求 meta → 输出包含轮次信息和只带 call_id 的 Codex Apps meta。
调用关系:它保证 Codex Apps 后端始终能把请求和工具调用 ID 对上。
调用图:调用 2 个内部函数(mcp_turn_metadata_context, make_session_and_context);外部调用 1 个(assert_eq!)。
codex_apps_auth_failure_result1223–1246 ↗
fn codex_apps_auth_failure_result() -> CallToolResult
作用:生成一份假的 Codex Apps 鉴权失败工具结果。它用来模拟连接器需要重新登录的情况。
数据流:无输入 → 构造 CallToolResult,内容提示重新认证,meta 里放认证失败细节 → 返回这份结果。
调用关系:多项 auth elicitation 测试用它作为触发条件,检查系统是否该向用户发起重新授权请求。
调用图:被 5 处调用(codex_apps_auth_elicitation_disallowed_by_policy_returns_original_result, codex_apps_auth_elicitation_feature_disabled_returns_original_result, codex_apps_auth_elicitation_feature_enabled_requests_elicitation, codex_apps_auth_elicitation_granular_mcp_disabled_returns_original_result, codex_apps_auth_elicitation_non_host_owned_server_returns_original_result);外部调用 2 个(json!, vec!)。
codex_apps_auth_failure_metadata1248–1256 ↗
fn codex_apps_auth_failure_metadata() -> McpToolApprovalMetadata
作用:生成鉴权失败场景下的连接器审批元数据,比如 Google Calendar 的名字和说明。
数据流:无输入 → 调用 approval_metadata 填入 connector_calendar、Google Calendar 等文字 → 返回元数据。
调用关系:鉴权引导测试用它让用户提示能显示可信的连接器名称,而不是结果 meta 里的不可信名字。
调用图:调用 1 个内部函数(approval_metadata);被 5 处调用(codex_apps_auth_elicitation_disallowed_by_policy_returns_original_result, codex_apps_auth_elicitation_feature_disabled_returns_original_result, codex_apps_auth_elicitation_feature_enabled_requests_elicitation, codex_apps_auth_elicitation_granular_mcp_disabled_returns_original_result, codex_apps_auth_elicitation_non_host_owned_server_returns_original_result)。
install_host_owned_codex_apps_manager1258–1291 ↗
async fn install_host_owned_codex_apps_manager(session: &Session, turn_context: &TurnContext)
作用:在测试会话里安装一个“宿主拥有”的 Codex Apps MCP 管理器。宿主拥有表示这个连接由 Codex 这边控制,才允许某些重新授权流程。
数据流:输入 session 和 turn_context → 读取认证信息、创建 McpConnectionManager → 存入 session.services.mcp_connection_manager。
调用关系:鉴权 elicitation 测试用它切换到可发起授权请求的运行环境。
调用图:调用 3 个内部函数(new, new, permission_profile);被 4 处调用(codex_apps_auth_elicitation_disallowed_by_policy_returns_original_result, codex_apps_auth_elicitation_feature_disabled_returns_original_result, codex_apps_auth_elicitation_feature_enabled_requests_elicitation, codex_apps_auth_elicitation_granular_mcp_disabled_returns_original_result);外部调用 7 个(new, new, new, default, codex_apps_tools_cache_key, get_tx_event, default)。
codex_apps_auth_elicitation_feature_disabled_returns_original_result1294–1312 ↗
async fn codex_apps_auth_elicitation_feature_disabled_returns_original_result()
作用:测试重新授权功能没开启时,鉴权失败结果会原样返回,不会弹授权请求。
数据流:安装宿主管理器并准备失败结果 → 调用 maybe_request_codex_apps_auth_elicitation → 得到原结果,事件通道没有新事件。
调用关系:它验证功能开关是 auth elicitation 的第一道门。
调用图:调用 4 个内部函数(codex_apps_auth_failure_metadata, codex_apps_auth_failure_result, install_host_owned_codex_apps_manager, make_session_and_context_with_rx);外部调用 2 个(assert!, assert_eq!)。
codex_apps_auth_elicitation_non_host_owned_server_returns_original_result1315–1337 ↗
async fn codex_apps_auth_elicitation_non_host_owned_server_returns_original_result()
作用:测试即使功能开启,如果不是宿主拥有的 Codex Apps 服务,也不会发起重新授权。
数据流:开启 AuthElicitation feature 但不安装宿主管理器 → 调用处理函数 → 原样返回并无事件。
调用关系:它保护授权流程只在可信控制的服务上运行。
调用图:调用 5 个内部函数(from, codex_apps_auth_failure_metadata, codex_apps_auth_failure_result, make_session_and_context_with_rx, with_defaults);外部调用 3 个(get_mut, assert!, assert_eq!)。
codex_apps_auth_elicitation_disallowed_by_policy_returns_original_result1340–1366 ↗
async fn codex_apps_auth_elicitation_disallowed_by_policy_returns_original_result()
作用:测试当前审批策略不允许询问时,鉴权失败不会变成授权弹窗。
数据流:安装宿主管理器、开启功能、把审批策略设为 Never → 处理失败结果 → 原样返回。
调用关系:它确认用户或系统的审批策略优先于自动授权引导。
调用图:调用 6 个内部函数(from, codex_apps_auth_failure_metadata, codex_apps_auth_failure_result, install_host_owned_codex_apps_manager, make_session_and_context_with_rx, with_defaults);外部调用 3 个(get_mut, assert!, assert_eq!)。
codex_apps_auth_elicitation_granular_mcp_disabled_returns_original_result1369–1401 ↗
async fn codex_apps_auth_elicitation_granular_mcp_disabled_returns_original_result()
作用:测试细粒度审批里关闭 MCP elicitation 时,不会发起 Codex Apps 重新授权请求。
数据流:开启功能并设置 Granular 策略但 mcp_elicitations=false → 调用处理 → 原结果返回、无事件。
调用关系:它覆盖细粒度权限设置对 auth elicitation 的限制。
调用图:调用 6 个内部函数(from, codex_apps_auth_failure_metadata, codex_apps_auth_failure_result, install_host_owned_codex_apps_manager, make_session_and_context_with_rx, with_defaults);外部调用 4 个(get_mut, Granular, assert!, assert_eq!)。
codex_apps_auth_elicitation_feature_enabled_requests_elicitation1404–1474 ↗
async fn codex_apps_auth_elicitation_feature_enabled_requests_elicitation()
作用:测试所有条件满足时,鉴权失败会变成一次给用户的重新授权请求,并在用户接受后返回提示重试的结果。
数据流:安装宿主管理器、开启功能、设置活动轮次 → 异步调用处理函数 → 收到 ElicitationRequest,模拟用户接受 → 输出“已请求认证,请重试”的工具结果。
调用关系:它是 Codex Apps auth elicitation 的完整成功路径测试,连接事件发送、用户响应解析和返回结果改写。
调用图:调用 7 个内部函数(from, codex_apps_auth_failure_metadata, codex_apps_auth_failure_result, install_host_owned_codex_apps_manager, make_session_and_context_with_rx, default, with_defaults);外部调用 8 个(clone, get_mut, String, assert!, assert_eq!, from_secs, spawn, timeout)。
mcp_tool_call_thread_id_meta_is_added_to_request_meta1477–1503 ↗
fn mcp_tool_call_thread_id_meta_is_added_to_request_meta()
作用:测试 MCP 工具调用请求 meta 会补上当前 threadId,并覆盖旧 threadId。
数据流:输入已有对象 meta、空 meta、非对象 meta 三种情况 → 调用 with_mcp_tool_call_thread_id_meta → 对象被写入 live threadId,非对象保持原样。
调用关系:它验证请求 meta 注入线程 ID 的小工具函数。
调用图:外部调用 1 个(assert_eq!)。
accepted_elicitation_content_converts_to_request_user_input_response1506–1524 ↗
fn accepted_elicitation_content_converts_to_request_user_input_response()
作用:测试 elicitation 返回的 JSON 内容能转换成旧式用户输入响应格式。
数据流:输入 content 里 approval=AcceptAndRemember → 转换 → 输出 answers map,里面有同样的审批答案。
调用关系:它连接新 elicitation 协议和已有 parse_mcp_tool_approval_response 解析逻辑。
调用图:外部调用 2 个(assert_eq!, json!)。
approval_elicitation_meta_marks_tool_approvals1527–1542 ↗
fn approval_elicitation_meta_marks_tool_approvals()
作用:测试审批 elicitation 的 meta 至少会标明这是 MCP 工具调用审批。
数据流:输入自定义服务且无额外信息 → 构造 meta → 输出只含 kind=mcp_tool_call 的 JSON。
调用关系:它验证 build_mcp_tool_approval_elicitation_meta 的最低字段要求。
调用图:外部调用 1 个(assert_eq!)。
approval_elicitation_meta_merges_session_and_always_persist_for_custom_servers1545–1575 ↗
fn approval_elicitation_meta_merges_session_and_always_persist_for_custom_servers()
作用:测试自定义服务允许两种记住方式时,meta 里会同时列出本会话和永久保存。
数据流:输入工具标题、说明、参数和两个记住开关 → 构造 meta → 输出包含 persist 数组和工具参数。
调用关系:它覆盖自定义 MCP 服务审批弹窗的附加说明数据。
调用图:外部调用 1 个(assert_eq!)。
guardian_mcp_review_request_includes_invocation_metadata1578–1616 ↗
fn guardian_mcp_review_request_includes_invocation_metadata()
作用:测试发给 Guardian 自动审核的 MCP 请求包含工具调用和连接器元数据。
数据流:输入 MCP invocation 和 connector/tool 元数据 → 构造 GuardianApprovalRequest → 输出包含 server、tool、arguments、connector 名称等字段。
调用关系:它保证自动审核模型拿到足够上下文,而不是只看到一个工具名。
调用图:调用 1 个内部函数(approval_metadata);外部调用 2 个(assert_eq!, json!)。
guardian_mcp_review_request_includes_annotations_when_present1619–1659 ↗
fn guardian_mcp_review_request_includes_annotations_when_present()
作用:测试如果 MCP 工具有安全注解,Guardian 审核请求会带上这些注解。
数据流:输入带 destructive、open_world、read_only 的元数据 → 构造审核请求 → 输出里的 annotations 映射成 GuardianMcpAnnotations。
调用关系:它让 Guardian 能基于工具风险提示做判断。
调用图:调用 1 个内部函数(annotations);外部调用 1 个(assert_eq!)。
guardian_review_decision_maps_to_mcp_tool_decision1662–1719 ↗
async fn guardian_review_decision_maps_to_mcp_tool_decision()
作用:测试 Guardian 的审核结论会正确转换成 MCP 工具审批决定。
数据流:输入 Approved、Denied、TimedOut、Abort 四种结论 → 转换 → Approved 变接受,拒绝和超时变带说明的拒绝,Abort 变无说明拒绝。
调用关系:它覆盖 mcp_tool_approval_decision_from_guardian,并检查拒绝理由从 session 中取出后会进入用户可见消息。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 4 个(new, assert!, assert_eq!, panic!)。
approval_elicitation_meta_includes_connector_source_for_codex_apps1722–1754 ↗
fn approval_elicitation_meta_includes_connector_source_for_codex_apps()
作用:测试 Codex Apps 审批 meta 会标明来源是连接器,并包含连接器和工具说明。
数据流:输入 calendar 连接器元数据和工具参数 → 构造 meta → 输出包含 source、connector_id、connector_name、description、tool params。
调用关系:它保证 Codex Apps 的审批弹窗能让用户知道具体是哪一个连接器在请求权限。
调用图:外部调用 1 个(assert_eq!)。
approval_elicitation_meta_merges_session_and_always_persist_with_connector_source1757–1793 ↗
fn approval_elicitation_meta_merges_session_and_always_persist_with_connector_source()
作用:测试 Codex Apps 连接器审批 meta 能同时包含连接器信息和两种记住方式。
数据流:输入连接器元数据、参数和两个记住开关 → 构造 meta → 输出既有 persist 数组,也有 connector 字段。
调用关系:它覆盖 Codex Apps 审批弹窗里“记住选择”和“来源说明”同时存在的情况。
调用图:外部调用 1 个(assert_eq!)。
declined_elicitation_response_stays_decline1796–1809 ↗
fn declined_elicitation_response_stays_decline()
作用:测试用户在 elicitation 外层动作里点拒绝时,即使内容里写着接受,也仍然按拒绝处理。
数据流:输入 action=Decline 且 content approval=Accept → 解析 → 输出 Decline。
调用关系:它保证外层用户动作优先,防止内容字段伪造接受。
调用图:外部调用 2 个(assert_eq!, json!)。
synthetic_decline_request_user_input_response_stays_decline1812–1826 ↗
fn synthetic_decline_request_user_input_response_stays_decline()
作用:测试旧式用户输入响应里的 synthetic decline 会被当作真正拒绝。
数据流:输入 answers 里 approval=synthetic decline → 解析 → 输出 Decline。
调用关系:它覆盖 parse_mcp_tool_approval_response 对特殊拒绝值的兼容处理。
调用图:外部调用 3 个(from, assert_eq!, vec!)。
accepted_elicitation_response_uses_always_persist_meta1829–1842 ↗
fn accepted_elicitation_response_uses_always_persist_meta()
作用:测试用户接受且 meta 要求永久保存时,解析结果是 AcceptAndRemember。
数据流:输入 action=Accept、meta persist=always → 解析 → 输出永久记住的接受决定。
调用关系:它验证 parse_mcp_tool_approval_elicitation_response 读取 meta 中的持久化意图。
调用图:外部调用 2 个(assert_eq!, json!)。
accepted_elicitation_response_uses_session_persist_meta1845–1858 ↗
fn accepted_elicitation_response_uses_session_persist_meta()
作用:测试用户接受且 meta 要求会话保存时,解析结果是 AcceptForSession。
数据流:输入 action=Accept、meta persist=session → 解析 → 输出本会话记住的接受决定。
调用关系:它和永久保存测试一起覆盖 elicitation 审批结果的记住模式。
调用图:外部调用 2 个(assert_eq!, json!)。
accepted_elicitation_without_content_defaults_to_accept1861–1872 ↗
fn accepted_elicitation_without_content_defaults_to_accept()
作用:测试用户接受但没有额外内容或 meta 时,默认就是普通接受。
数据流:输入 action=Accept、content=None、meta=None → 解析 → 输出 Accept。
调用关系:它提供 elicitation 响应解析的默认行为保证。
调用图:外部调用 1 个(assert_eq!)。
persist_codex_app_tool_approval_writes_tool_override1875–1917 ↗
async fn persist_codex_app_tool_approval_writes_tool_override()
作用:测试把 Codex App 工具永久批准时,会把对应工具配置写进 config.toml。
数据流:创建临时 codex_home 和配置 → 调用 persist_codex_app_tool_approval → 读取 TOML,确认 apps.calendar.tools 中 approval_mode=Approve。
调用关系:它覆盖 Codex Apps 审批持久化到用户配置文件的写入格式。
调用图:外部调用 6 个(assert!, assert_eq!, default, read_to_string, tempdir, from_str)。
persist_custom_mcp_tool_approval_writes_tool_override1920–1952 ↗
async fn persist_custom_mcp_tool_approval_writes_tool_override()
作用:测试把自定义 MCP 工具永久批准时,会写入该 MCP server 下的工具覆盖配置。
数据流:先写一个 docs MCP 服务配置 → 调用 persist_custom_mcp_tool_approval → 重新读 TOML,确认 docs.search 工具被设为 Approve。
调用关系:它验证自定义 MCP 服务器的审批持久化格式。
调用图:外部调用 7 个(assert!, assert_eq!, default, read_to_string, write, tempdir, from_str)。
custom_mcp_tool_approval_mode_uses_server_default_with_tool_override1955–1989 ↗
async fn custom_mcp_tool_approval_mode_uses_server_default_with_tool_override()
作用:测试自定义 MCP 工具审批模式会先看工具覆盖,没有覆盖时使用服务器默认值。
数据流:写入 server 默认 approve 和 search 工具 prompt → 加载配置 → 查询 read、search、unknown 三种工具 → 得到 Approve、Prompt、Auto。
调用关系:它覆盖 custom_mcp_tool_approval_mode 的配置优先级。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 5 个(new, assert_eq!, default, write, tempdir)。
custom_mcp_tool_approval_mode_uses_plugin_mcp_policy1992–2029 ↗
async fn custom_mcp_tool_approval_mode_uses_plugin_mcp_policy()
作用:测试插件里的 MCP 服务也能使用插件配置中声明的审批策略。
数据流:创建 sample 插件并写插件 MCP 策略 → 加载配置、清缓存 → 查询 read 和 search → 分别得到默认 Prompt 和工具覆盖 Approve。
调用关系:它验证 custom_mcp_tool_approval_mode 会查插件来源的 MCP 配置。
调用图:调用 2 个内部函数(write_sample_plugin_mcp, make_session_and_context);外部调用 4 个(new, assert_eq!, default, write)。
custom_mcp_tool_approval_mode_uses_updated_plugin_mcp_policy_after_cache_warm2032–2082 ↗
async fn custom_mcp_tool_approval_mode_uses_updated_plugin_mcp_policy_after_cache_warm()
作用:测试插件缓存已经预热后,更新配置仍能让审批模式使用新策略。
数据流:先加载无工具策略的插件配置并预热缓存 → 更新 config.toml 加入 search=approve → 重新加载配置并查询 → 得到 Approve。
调用关系:它防止插件管理器缓存导致审批策略过期。
调用图:调用 2 个内部函数(write_sample_plugin_mcp, make_session_and_context);外部调用 4 个(new, assert_eq!, default, write)。
maybe_persist_mcp_tool_approval_reloads_session_config2085–2121 ↗
async fn maybe_persist_mcp_tool_approval_reloads_session_config()
作用:测试记住 Codex App 工具审批后,session 里的配置会重新加载,并且立刻能查到已记住。
数据流:创建审批 key → 调用 maybe_persist_mcp_tool_approval → 从 session.get_config 读有效配置 → 确认工具 approval_mode=Approve 且 remembered=true。
调用关系:它覆盖持久化写盘之后同步刷新运行中配置的行为。
调用图:调用 1 个内部函数(make_session_and_context);外部调用 3 个(assert_eq!, deserialize, create_dir_all)。
maybe_persist_mcp_tool_approval_reloads_session_config_for_custom_server2124–2169 ↗
async fn maybe_persist_mcp_tool_approval_reloads_session_config_for_custom_server()
作用:测试自定义 MCP 服务的审批持久化后,运行中 session 配置也会刷新。
数据流:写入 docs MCP server 初始配置 → 调用 maybe_persist_mcp_tool_approval → 读 session 有效配置 → 确认 docs/search 被记住。
调用关系:它是 Codex Apps 配置刷新测试在自定义 MCP 服务上的对应版本。
调用图:调用 2 个内部函数(without_managed_config_for_tests, make_session_and_context);外部调用 5 个(new, deserialize, assert_eq!, create_dir_all, write)。
maybe_persist_mcp_tool_approval_writes_plugin_mcp_policy2172–2219 ↗
async fn maybe_persist_mcp_tool_approval_writes_plugin_mcp_policy()
作用:测试插件 MCP 工具被永久批准时,会写到插件自己的配置分区里。
数据流:创建 sample 插件并启用 → 调用 maybe_persist_mcp_tool_approval → 读取 config.toml → 确认 plugins."sample@test".mcp_servers.sample.tools.search 被设为 Approve。
调用关系:它连接审批记住逻辑和插件配置写入路径。
调用图:调用 2 个内部函数(write_sample_plugin_mcp, make_session_and_context);外部调用 7 个(new, assert!, assert_eq!, default, read_to_string, write, from_str)。
maybe_persist_mcp_tool_approval_writes_project_config_for_project_server2222–2274 ↗
async fn maybe_persist_mcp_tool_approval_writes_project_config_for_project_server()
作用:测试项目级 MCP server 的审批记住会写进项目的 .codex/config.toml,而不是用户全局配置。
数据流:创建临时项目、项目配置和信任记录 → 加载项目配置 → 记住 docs/search → 读取项目配置文件并确认工具审批写入。
调用关系:它验证持久化逻辑能识别配置来源,避免把项目专属许可写错地方。
调用图:调用 2 个内部函数(new, make_session_and_context);外部调用 9 个(new, assert!, assert_eq!, default, create_dir_all, read_to_string, write, tempdir, from_str)。
approve_mode_skips_when_annotations_do_not_require_approval2277–2315 ↗
async fn approve_mode_skips_when_annotations_do_not_require_approval()
作用:测试 Approve 模式下,如果工具注解显示不需要审批,系统不会额外请求批准。
数据流:输入只读工具 invocation 和元数据 → 调用 maybe_request_mcp_tool_approval,模式为 Approve → 输出 None,表示直接继续。
调用关系:它覆盖审批请求主流程里“安全只读工具跳过审批”的分支。
调用图:调用 3 个内部函数(annotations, make_session_and_context, new);外部调用 2 个(new, assert_eq!)。
guardian_mode_skips_auto_when_annotations_do_not_require_approval2318–2389 ↗
async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval()
作用:测试 AutoReview 模式下,只读安全工具不会触发 Guardian 自动审核。
数据流:设置 mock 模型服务器期望零请求 → 配置 AutoReview → 调用 maybe_request_mcp_tool_approval → 输出 None 且 mock 没收到请求。
调用关系:它保证低风险 MCP 工具不会浪费自动审核调用。
调用图:调用 5 个内部函数(annotations, make_session_and_context, models_manager_with_provider, new, start_mock_server);外部调用 7 个(clone, new, given, new, assert_eq!, create_model_provider, format!)。
permission_request_hook_allows_mcp_tool_call2392–2472 ↗
async fn permission_request_hook_allows_mcp_tool_call()
作用:测试 PermissionRequest 钩子返回允许时,MCP 工具调用会被接受,并且钩子拿到完整输入。
数据流:安装允许钩子 → 调用 maybe_request_mcp_tool_approval → 得到 Accept → 读取钩子日志,确认 session、turn、cwd、工具名和参数都正确。
调用关系:它覆盖 MCP 审批流程中外部钩子的允许路径。
调用图:调用 4 个内部函数(annotations, install_mcp_permission_request_hook, make_session_and_context, new);外部调用 4 个(new, assert_eq!, json!, read_to_string)。
permission_request_hook_uses_hook_tool_name_without_metadata2475–2529 ↗
async fn permission_request_hook_uses_hook_tool_name_without_metadata()
作用:测试没有 MCP 元数据时,PermissionRequest 钩子仍然使用传入的 hook tool name。
数据流:安装允许钩子并传 metadata=None → 请求审批 → 读取日志 → 确认 tool_name 是 mcp__memory__create_entities。
调用关系:它保证钩子不依赖审批元数据也能正确匹配工具名。
调用图:调用 3 个内部函数(install_mcp_permission_request_hook, make_session_and_context, new);外部调用 4 个(new, assert_eq!, json!, read_to_string)。
permission_request_hook_runs_after_remembered_mcp_approval2532–2592 ↗
async fn permission_request_hook_runs_after_remembered_mcp_approval()
作用:测试已经在会话里记住的 MCP 审批会直接放行,并跳过 PermissionRequest 钩子。
数据流:安装一个会拒绝的钩子 → 先把工具审批 key 记住 → 再请求审批 → 得到 Accept,且钩子日志文件不存在。
调用关系:它验证审批记忆优先于钩子,避免已允许的工具反复触发外部脚本。
调用图:调用 4 个内部函数(annotations, install_mcp_permission_request_hook, make_session_and_context, new);外部调用 4 个(new, assert!, assert_eq!, json!)。
guardian_mode_mcp_denial_returns_rationale_message2595–2680 ↗
async fn guardian_mode_mcp_denial_returns_rationale_message()
作用:测试 Guardian 自动审核拒绝 MCP 工具时,返回给审批流程的拒绝决定包含模型给出的理由。
数据流:用 mock SSE 返回 deny 和 rationale → 配置 AutoReview → 请求危险工具审批 → 得到 Decline,并检查消息里有拒绝原因和防绕过提示。
调用关系:它覆盖 maybe_request_mcp_tool_approval 里 Guardian 拒绝路径和说明文本生成。
调用图:调用 7 个内部函数(annotations, make_session_and_context, models_manager_with_provider, new, mount_sse_once, sse, start_mock_server);外部调用 9 个(clone, new, assert!, assert_eq!, create_model_provider, format!, panic!, json!, vec!)。
prompt_mode_waits_for_approval_when_annotations_do_not_require_approval2683–2735 ↗
async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval()
作用:测试 Prompt 模式即使工具看起来只读安全,也会等待用户确认。
数据流:创建活动轮次和只读工具 → 异步调用审批 → 200 毫秒内不返回,说明正在等用户输入 → 测试结束时取消任务。
调用关系:它区分 Prompt 模式和 Auto/Approve 模式,确认显式提示模式不会自动跳过。
调用图:调用 4 个内部函数(annotations, make_session_and_context_with_rx, default, new);外部调用 3 个(clone, assert!, spawn)。
full_access_mode_skips_mcp_tool_approval_for_all_approval_modes2738–2784 ↗
async fn full_access_mode_skips_mcp_tool_approval_for_all_approval_modes()
作用:测试 full access 权限下,不管工具审批模式是 Auto、Prompt 还是 Approve,都跳过 MCP 工具审批。
数据流:把 approval_policy 设为 Never,并禁用 PermissionProfile → 对三种 approval_mode 调用审批 → 全部输出 None。
调用关系:它覆盖最高权限模式对 MCP 审批流程的总开关效果。
调用图:调用 3 个内部函数(annotations, make_session_and_context, new);外部调用 3 个(new, assert_eq!, json!)。
approve_mode_skips_guardian_in_every_permission_mode2787–2872 ↗
async fn approve_mode_skips_guardian_in_every_permission_mode()
作用:测试 Approve 模式在各种全局审批策略下都不会触发 Guardian 或用户审核。
数据流:为多种 AskForApproval 策略循环建会话和 mock 服务器 → 调用 maybe_request_mcp_tool_approval,模式为 Approve → 每次都返回 None,mock 期望无请求。
调用关系:它确认工具级 Approve 配置是明确放行,不会被不同全局权限模式重新送审。
调用图:调用 7 个内部函数(annotations, make_session_and_context, auth_manager_from_auth, models_manager_with_provider, new, start_mock_server, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(clone, new, given, new, Granular, assert_eq!, create_model_provider, format!, json!)。
core/src/unified_exec/async_watcher_tests.rs源码 ↗
这个测试文件关心的是一件很实际的事:程序从异步任务里读到一串字节后,可能需要分批拿出来处理,但文字在电脑里并不总是“一个字等于一个字节”。比如英文通常一个字节,像“é”这种字符在 UTF-8(一种常见的文字编码方式)里要两个字节。如果切的时候只看长度,就可能把一个字符切成两半,后面显示或解析就会出错。这里的三个测试分别检查:普通英文会按最大字节数切;多字节字符不会被拦腰切断;即使遇到非法 UTF-8 字节,也至少能切出一点内容往前走,避免一直停在原地。它像是在检查一把“切面包刀”:既要按规定大小切,又不能把一片面包撕烂,还不能碰到硬壳就完全不动。
split_valid_utf8_prefix_respects_max_bytes_for_ascii6–18 ↗
fn split_valid_utf8_prefix_respects_max_bytes_for_ascii()
作用:这个测试确认:面对普通英文字符时,切分函数会严格遵守“最多取多少字节”的限制。它防止函数一次拿太多,破坏后续分批处理的节奏。
数据流:进去的是一段字节内容 hello word! 和最大长度 5。测试调用 split_valid_utf8_prefix_with_max 后,期望第一次拿出 hello,原缓冲区剩下 word!;再调用一次,期望拿出 word,剩下 !。出来的结果不是返回给业务使用,而是通过断言检查“切出来的部分”和“剩下的部分”都对不对。
调用关系:这个测试在测试运行时由 Rust 的测试框架自动调用。它把准备好的字节交给 split_valid_utf8_prefix_with_max,再用 assert_eq! 做对比,确认这个底层切分工具在最简单的 ASCII 英文场景下行为正确。
调用图:外部调用 2 个(assert_eq!, split_valid_utf8_prefix_with_max)。
split_valid_utf8_prefix_avoids_splitting_utf8_codepoints21–29 ↗
fn split_valid_utf8_prefix_avoids_splitting_utf8_codepoints()
作用:这个测试确认:切分函数不会把一个 UTF-8 字符切成两半。它保护的是非英文文字或特殊字符的完整性,避免后面出现乱码或解析失败。
数据流:进去的是字符串 ééé 转成的字节,以及最大长度 3。因为每个 é 占 2 个字节,虽然上限是 3,但安全的切法只能取出第一个 é 的 2 个字节。测试检查取出的字节能还原成 é,同时原缓冲区还剩两个 é。
调用关系:这个测试同样由测试框架自动执行。它专门把多字节字符交给 split_valid_utf8_prefix_with_max,再用 assert_eq! 验证结果,补上普通英文测试覆盖不到的关键场景。
调用图:外部调用 2 个(assert_eq!, split_valid_utf8_prefix_with_max)。
split_valid_utf8_prefix_makes_progress_on_invalid_utf832–39 ↗
fn split_valid_utf8_prefix_makes_progress_on_invalid_utf8()
作用:这个测试确认:即使开头遇到非法 UTF-8 字节,切分函数也会前进一步,而不是一直卡在那里。这样可以避免处理输出流时因为一个坏字节导致整个流程停住。
数据流:进去的是三个字节:一个非法字节 0xff,后面跟着 a 和 b,最大长度是 2。测试调用切分函数后,期望它先取出这个非法字节,并把缓冲区推进到只剩 ab。出来的效果是:坏数据被单独移走,后面的正常数据还能继续处理。
调用关系:这个测试在测试阶段自动运行。它用 vec! 准备包含坏字节的输入,把输入交给 split_valid_utf8_prefix_with_max,再用 assert_eq! 检查函数是否真的“往前走了”。它验证的是异常数据场景下的安全性和抗卡死能力。
调用图:外部调用 3 个(assert_eq!, split_valid_utf8_prefix_with_max, vec!)。
core/src/unified_exec/head_tail_buffer_tests.rs源码 ↗
有些命令或程序会吐出很多字节,如果全存下来,内存可能被撑爆;但如果直接截断,又可能丢掉开头的错误背景或结尾的关键结果。HeadTailBuffer 就像一本太厚的日志只撕下第一页和最后一页保存,中间省略并记下省略了多少。这个测试文件用不同场景检查它是否靠谱:容量够时应完整保存;容量超了应保留前半段和后半段;容量为 0 时什么都不留;清空后状态要归零;新来的大块内容太长时,只留下它最后的部分。文件本身不实现功能,而是用断言(检查实际结果是不是预期结果)来防止以后改代码时把这些边界行为改坏。
keeps_prefix_and_suffix_when_over_budget6–19 ↗
fn keeps_prefix_and_suffix_when_over_budget()
作用:这个测试确认:当放进去的数据超过容量上限时,缓冲区不会简单保留前面或后面,而是同时保留开头和结尾,中间标记为被省略。
数据流:先创建一个最多保存 10 个字节的 HeadTailBuffer,再放入 “0123456789”,这时刚好装满,没有省略。接着再放入 “ab”,总量超了。测试把缓冲区内容转成文字后检查:结果仍然以 “01234” 开头,并以 “89ab” 结尾,同时省略字节数大于 0。
调用关系:这是验证 HeadTailBuffer 核心行为的测试之一。它调用 new 建出缓冲区,再通过 push_chunk 喂数据,最后用 omitted_bytes 和 to_bytes 看结果是否符合“留头留尾”的规则。
调用图:调用 1 个内部函数(new);外部调用 3 个(from_utf8_lossy, assert!, assert_eq!)。
max_bytes_zero_drops_everything22–30 ↗
fn max_bytes_zero_drops_everything()
作用:这个测试确认:如果容量上限是 0,缓冲区应该完全不保存任何内容,但要正确记录有多少字节被丢掉。
数据流:测试创建一个最大容量为 0 的 HeadTailBuffer,然后放入 “abc”。之后检查保留字节数是 0,省略字节数是 3,转出的字节是空的,快照里的数据块也是空的。
调用关系:这是一个边界情况测试,专门防止“容量为 0”这种极端设置出错。它使用 new 创建对象,再用 retained_bytes、omitted_bytes、to_bytes 和 snapshot_chunks 检查内部状态对外表现是否一致。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。
head_budget_zero_keeps_only_last_byte_in_tail33–40 ↗
fn head_budget_zero_keeps_only_last_byte_in_tail()
作用:这个测试确认:当总容量只有 1 个字节时,缓冲区最后只能留下最新内容的最后 1 个字节。
数据流:测试创建一个最大容量为 1 的 HeadTailBuffer,放入 “abc”。因为只能保存 1 个字节,所以最后只应该留下 “c”,另外 2 个字节算作被省略。
调用关系:它验证的是容量极小的时候,HeadTailBuffer 是否仍按“尽量保留尾部关键信息”的思路工作。它通过 new 和 push_chunk 准备数据,再用 retained_bytes、omitted_bytes、to_bytes 检查结果。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。
draining_resets_state43–54 ↗
fn draining_resets_state()
作用:这个测试确认:把缓冲区里的数据取走之后,缓冲区应该像新的一样清空,不能残留旧的计数或内容。
数据流:测试先创建容量为 10 的缓冲区,放入超过容量的数据,让它进入有内容、有省略的状态。然后调用 drain_chunks 把保留的数据块取出来,并检查确实取到了东西。最后再检查缓冲区的保留字节数、省略字节数和输出内容都变回 0 或空。
调用关系:这个测试覆盖的是“取走并重置”流程。它会在 HeadTailBuffer 已经处理过数据后调用 drain_chunks,确保这个操作不仅返回数据,也会清理状态,避免下一轮使用时混入上一轮的内容。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert!, assert_eq!)。
chunk_larger_than_tail_budget_keeps_only_tail_end57–68 ↗
fn chunk_larger_than_tail_budget_keeps_only_tail_end()
作用:这个测试确认:如果后来塞进来的单个数据块本身就比尾部预留空间还大,缓冲区应该只保留这个新数据块的最后一段。
数据流:测试先创建容量为 10 的缓冲区,并放入 “0123456789”。按设计,头部大约保留 5 个字节,尾部也保留 5 个字节。然后放入很长的 “ABCDEFGHIJK”。最后检查输出仍以 “01234” 开头,但结尾变成这个新块的最后 5 个字节 “GHIJK”,并且确实记录了省略内容。
调用关系:它测试的是 push_chunk 面对“大块新数据”时的替换逻辑。这个场景很重要,因为真实程序输出可能一次吐出很长一段,缓冲区必须能丢掉旧尾巴并保留最新结尾。
调用图:调用 1 个内部函数(new);外部调用 2 个(from_utf8_lossy, assert!)。
fills_head_then_tail_across_multiple_chunks71–89 ↗
fn fills_head_then_tail_across_multiple_chunks()
作用:这个测试确认:缓冲区不是只按单次输入工作,而是能在多次小块输入之间正确累计,先填满开头部分,再填尾部部分,超出后再滚动丢掉最旧的尾部内容。
数据流:测试创建容量为 10 的缓冲区。先分两次放入 “01” 和 “234”,检查合起来正好形成头部 “01234”。再放入 “567” 和 “89”,检查 10 个字节都完整保留且没有省略。最后再放入 “a”,容量超出,尾部最旧的一个字节被丢掉,结果变成 “012346789a”,省略字节数变为 1。
调用关系:这是对连续使用流程的测试。它模拟真实输出一段一段到来的情况,反复调用 push_chunk,并在每一步用 to_bytes 和 omitted_bytes 确认 HeadTailBuffer 的头部、尾部和省略计数都按预期变化。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。
core/src/unified_exec/process_manager_tests.rs源码 ↗
这不是正式运行时会直接用的功能文件,而是给开发者和持续集成系统用的测试文件。它测试的是一个会启动和跟踪外部命令的“统一执行”流程:命令运行时要带上稳定的环境变量,比如关闭颜色、固定语言;如果交给执行服务器,还要只传真正发生变化的环境;网络被沙箱拦住时,要给用户清楚的提示;命令一开始就失败时,也要发出一条完整的结束事件,不能让前端一直等着。文件后半部分还检查进程太多时该删谁:优先删已经退出、且不是最近常用的进程;如果没有退出的,就删最久没用的。整体上,它像给一台命令执行机器做“防回归体检”,防止小改动悄悄破坏用户能感知的行为。
unified_exec_env_injects_defaults8–24 ↗
fn unified_exec_env_injects_defaults()
作用:这个测试确认:当没有传任何环境变量时,统一执行会自动补上一组安全、稳定的默认值。这样命令输出更可预测,比如不乱带颜色、不弹分页器。
数据流:进去的是一个空的环境变量表 → 测试调用统一执行的环境补全逻辑 → 出来应该是一张固定的表,里面有 NO_COLOR、TERM、LANG、PAGER 等默认设置;测试把实际结果和期望结果逐项比对。
调用关系:它直接验证环境准备这一步的最基础约定。后续真正执行命令时会依赖这些默认值,让不同机器上的命令输出尽量一致。
调用图:外部调用 4 个(from, new, new, assert_eq!)。
unified_exec_env_overrides_existing_values27–36 ↗
fn unified_exec_env_overrides_existing_values()
作用:这个测试确认:统一执行要求固定的环境变量,会覆盖调用者原来传的值;但无关变量会保留下来。这样既能保证执行行为统一,又不会把 PATH 这类必要信息弄丢。
数据流:进去的是一张已有环境变量表,里面 NO_COLOR 是 0,PATH 是 /usr/bin → 环境补全逻辑改写 NO_COLOR → 出来时 NO_COLOR 变成 1,PATH 仍然保持原样。
调用关系:它补充验证环境准备逻辑的覆盖规则,和 unified_exec_env_injects_defaults 一起确保“默认值”和“用户已有值”的冲突处理不会出错。
调用图:外部调用 2 个(new, assert_eq!)。
env_overlay_for_exec_server_keeps_runtime_changes_only39–67 ↗
fn env_overlay_for_exec_server_keeps_runtime_changes_only()
作用:这个测试确认:发给执行服务器的环境变量只包含运行时真正变动的部分,而不是把客户端原始环境全量再发一遍。这样可以避免重复、泄露或错误覆盖服务器端自己的环境设置。
数据流:进去有两份环境:一份是本地策略算出的环境,一份是这次请求真正要用的环境 → 测试调用“求差异”的逻辑 → 出来只留下 PATH 的变化和 CODEX_THREAD_ID、CODEX_SANDBOX_NETWORK_DISABLED 这类新增项,HOME 这种没变的项被过滤掉。
调用关系:它验证执行服务器参数构造前的一道筛选步骤。exec_server_params_use_path_uri_and_env_policy_overlay_contract 进一步检查这个筛选结果会被正确放进请求参数里。
调用图:外部调用 2 个(from, assert_eq!)。
exec_server_params_use_path_uri_and_env_policy_overlay_contract70–128 ↗
fn exec_server_params_use_path_uri_and_env_policy_overlay_contract()
作用:这个测试确认:把一次本地执行请求转换成执行服务器请求时,进程编号、当前目录、环境策略和环境差异都按双方约定的格式传过去。没有这个约定,客户端和执行服务器可能会对同一次命令理解不一致。
数据流:进去是一份 ExecRequest,里面有命令、工作目录、沙箱策略、环境变量和执行服务器环境配置 → 测试调用参数转换函数 → 出来得到服务器参数:进程编号变成字符串,工作目录变成路径 URI(一种适合跨进程传递路径的格式),环境策略存在,env 里只保留相对本地策略发生变化的变量。
调用关系:它站在“客户端准备把命令交给执行服务器”的边界上做检查。它依赖路径转换、沙箱策略对象和环境差异规则,确保这些零件组装出来的请求符合执行服务器的接口契约。
调用图:调用 1 个内部函数(unrestricted);外部调用 7 个(from, new, new, assert!, assert_eq!, current_dir, vec!)。
exec_server_process_id_matches_unified_exec_process_id131–133 ↗
fn exec_server_process_id_matches_unified_exec_process_id()
作用:这个测试确认:统一执行内部用的数字进程编号,传给执行服务器时只是转成同样内容的字符串,不会加前缀、改格式或重新编号。
数据流:进去是数字 4321 → 转换函数把它变成服务器使用的进程编号 → 出来应该正好是字符串 "4321"。
调用关系:它保护进程编号的一致性。这样前端、统一执行管理器和执行服务器说到同一个 process_id 时,指的就是同一个进程。
调用图:外部调用 1 个(assert_eq!)。
initial_exec_yield_time_uses_windows_floor137–149 ↗
fn initial_exec_yield_time_uses_windows_floor()
作用:这个测试只在 Windows 上运行,用来确认首次等待命令输出的时间不能低于 Windows 需要的下限。因为 Windows 上进程启动和终端准备可能更慢,等太短容易误判。
数据流:进去是几个用户请求的等待毫秒数 → clamp_yield_time 会把太小的值抬到 Windows 的最低值,把太大的值压到最大允许值 → 出来分别验证 1000 被抬高、10000 保持不变、超过最大值被截断。
调用关系:它验证平台专门规则。和 initial_exec_yield_time_has_no_platform_floor 是一对:一个检查 Windows 特例,一个检查非 Windows 的普通规则。
调用图:外部调用 1 个(assert_eq!)。
initial_exec_yield_time_has_no_platform_floor153–159 ↗
fn initial_exec_yield_time_has_no_platform_floor()
作用:这个测试在非 Windows 系统上运行,用来确认等待时间没有 Windows 那个额外下限,只遵守通用的最小值。这样 Linux、macOS 等平台不会被无故拖慢。
数据流:进去是用户请求的等待毫秒数 → clamp_yield_time 只做通用范围限制 → 出来时 1000 保持 1000,太小的 1 被提高到系统允许的最小值。
调用关系:它和 Windows 版本的测试一起覆盖 clamp_yield_time 的跨平台行为,防止开发者改一个平台时误伤另一个平台。
调用图:外部调用 1 个(assert_eq!)。
network_denial_fallback_message_names_sandbox_network_proxy162–169 ↗
async fn network_denial_fallback_message_names_sandbox_network_proxy()
作用:这个测试确认:当网络访问被拒绝、又没有更具体上下文时,系统会告诉用户是 Codex 沙箱网络代理拦住了网络。这样错误信息不会含糊地只说“失败”。
数据流:进去没有会话信息,也没有延迟得到的额外说明 → 异步获取网络拒绝提示 → 出来是一句固定文案,明确点名 sandbox network proxy,也就是沙箱里的网络代理。
调用关系:它测试网络拒绝提示的兜底路径。这个提示会在命令因为网络限制失败时展示给用户,帮助用户理解不是命令本身莫名坏了。
调用图:外部调用 1 个(assert_eq!)。
late_network_denial_grace_observes_cancellation_after_exit172–181 ↗
async fn late_network_denial_grace_observes_cancellation_after_exit()
作用:这个测试确认:命令退出后,如果系统还短暂等待“网络被拒绝”的信号,那么在这段宽限期里收到取消信号时能立刻认出来。这样不会错过刚刚才到达的网络拦截结果。
数据流:进去是一个取消令牌(一种可以通知异步任务停止或发生事件的小开关)→ 测试启动一个后台任务,10 毫秒后触发取消 → 等待函数在宽限期里观察这个信号 → 出来返回 true,表示确实捕捉到了这次取消。
调用关系:它验证网络拒绝检测里的异步等待行为。tokio 的后台任务负责模拟“稍后才来的信号”,wait_for_late_network_denial 负责在命令结束后的短时间窗口里接住它。
调用图:外部调用 5 个(new, from_millis, assert!, spawn, sleep)。
failed_initial_end_for_unstored_process_uses_fallback_output184–258 ↗
async fn failed_initial_end_for_unstored_process_uses_fallback_output()
作用:这个测试确认:如果命令一开始就失败,而且这个进程还没来得及存进进程管理器,系统仍然会发出一条失败结束事件,并带上可读的失败输出。否则调用方可能永远等不到“命令结束”的通知。
数据流:进去是一套测试会话、一次执行请求、已有的部分输出、一个预先准备的拒绝标记和错误消息 → 函数尝试为未存储的失败进程补发结束事件 → 出来事件通道里收到 ExecCommandEnd:状态是失败,退出码是 -1,process_id 是 123,聚合输出是预设标记加上网络拒绝信息。
调用关系:它模拟统一执行早期失败的边角情况。测试先用 make_session_and_context_with_rx 搭出一个能收事件的会话,再调用 emit_failed_initial_exec_end_if_unstored,最后从事件通道取出结果核对,确保前端或上层流程不会卡住。
调用图:调用 3 个内部函数(make_session_and_context_with_rx, new, default);外部调用 9 个(clone, new, from_millis, from_secs, assert_eq!, panic!, new, timeout, vec!)。
pruning_prefers_exited_processes_outside_recently_used261–279 ↗
fn pruning_prefers_exited_processes_outside_recently_used()
作用:这个测试确认:当进程管理器需要腾位置时,会优先清理已经退出、并且不属于最近使用的一批进程。这样既释放资源,又尽量不影响用户刚刚还可能查看的进程。
数据流:进去是一组进程元信息:进程编号、最后使用时间、是否已退出 → 清理候选选择函数查找该删谁 → 出来选择进程 2,因为它已经退出,而且不在最近保护范围内。
调用关系:它测试 UnifiedExecProcessManager 的淘汰策略。这个策略在进程数量太多时会被用到,避免后台保存的进程记录无限增长。
调用图:外部调用 4 个(now, assert_eq!, process_id_to_prune_from_meta, vec!)。
pruning_falls_back_to_lru_when_no_exited282–300 ↗
fn pruning_falls_back_to_lru_when_no_exited()
作用:这个测试确认:如果没有任何进程已经退出,那就退而求其次,删除最久没有被使用的进程。LRU 可以理解成“最久没碰过的先收走”。
数据流:进去是一组全都还没退出的进程元信息 → 选择函数找不到已退出候选 → 出来选择最后使用时间最早的进程 1。
调用关系:它验证清理策略的备用规则。当前面的“优先删已退出进程”没法用时,进程管理器仍然能选出一个合理对象来控制数量。
调用图:外部调用 4 个(now, assert_eq!, process_id_to_prune_from_meta, vec!)。
pruning_protects_recent_processes_even_if_exited303–322 ↗
fn pruning_protects_recent_processes_even_if_exited()
作用:这个测试确认:最近使用的一批进程会被保护起来,即使其中有的已经退出,也不会优先删除。这样用户刚运行完、可能马上要继续查看输出的进程不会被太快清掉。
数据流:进去是一组进程元信息,其中进程 10 已退出但很新,进程 3 也已退出且处在最近保护范围内 → 选择函数跳过这些最近进程 → 出来选择更老的进程 1。
调用关系:它补上清理策略的一个重要例外:不能只看“是否退出”,还要看“最近有没有被用过”。它和另外两个 pruning 测试一起说明完整的删减优先级。
调用图:外部调用 4 个(now, assert_eq!, process_id_to_prune_from_meta, vec!)。
core/src/unified_exec/process_tests.rs源码 ↗
这个文件不是真正启动系统命令,而是做了一个 MockExecProcess,也就是“假进程”。它像舞台道具一样,只返回测试提前安排好的结果:写入是否成功、读取时是否已经退出、终止时是否报错。测试把这个假进程包装成 UnifiedExecProcess,然后模拟几种容易出错的情况。比如远程服务说“找不到这个进程”或“标准输入已关闭”时,本地对象应该立刻标记为已退出;手动标记失败并终止时,第一次失败原因不能被后来的原因盖掉;请求终止远程进程时,只有远程确认成功,本地才算退出;如果进程刚启动就已经退出,也要能通过唤醒通知读到退出码。整体上,这个文件是在给进程状态这套安全网做验收,确保上层代码看到的是可信状态。
MockExecProcess::process_id55–57 ↗
fn process_id(&self) -> &ProcessId
作用:返回这个假进程的编号。测试里的 UnifiedExecProcess 需要像面对真实远程进程一样,能拿到一个进程身份。
数据流:进去的是对 MockExecProcess 的读取请求 → 它直接取出结构体里保存的 process_id → 出来的是这个进程编号的引用,不改动任何状态。
调用关系:它是 ExecProcess 接口的一部分。UnifiedExecProcess 把假进程当成真实远程进程使用时,可以通过这个入口确认自己正在操作哪一个进程。
MockExecProcess::subscribe_wake59–61 ↗
fn subscribe_wake(&self) -> watch::Receiver<u64>
作用:提供一个“唤醒通知”的订阅口。可以把它理解成门铃:远程进程有新输出或状态变化时,按一下门铃,等待方就会醒来查看。
数据流:进去的是对假进程的订阅请求 → 它从 wake_tx 这个通知发送器上新建一个接收端 → 出来的是 watch::Receiver,之后别人可以用它等待变化。
调用关系:它调用外部的 subscribe 来产生订阅者。UnifiedExecProcess 在等待远程进程状态变化时会用这类通知;本文件的早退测试会手动发送一次通知,让启动流程去读取退出信息。
调用图:外部调用 1 个(subscribe)。
MockExecProcess::subscribe_events63–65 ↗
fn subscribe_events(&self) -> ExecProcessEventReceiver
作用:返回一个空的事件流。这个测试文件不关心详细事件,只关心读写和退出状态,所以这里故意不给任何额外事件。
数据流:进去的是订阅事件的请求 → 它创建一个空的 ExecProcessEventReceiver → 出来的是不会产生事件的接收器。
调用关系:它调用 empty 来做一个空事件源。这样 MockExecProcess 仍然满足 ExecProcess 接口,但测试重点不会被无关事件干扰。
调用图:调用 1 个内部函数(empty)。
MockExecProcess::read67–74 ↗
fn read(
&self,
_after_seq: Option<u64>,
_max_bytes: Option<usize>,
_wait_ms: Option<u64>,
) -> ExecProcessFuture<'_, ReadResponse>
作用:模拟从远程进程读取输出和退出状态。测试可以提前塞入一批读取结果,让 UnifiedExecProcess 像读真实进程一样读到它们。
数据流:进去的是读取请求,参数里可以有起始序号、最大字节数、等待时间,但这个假实现会忽略它们 → 它返回一个异步任务,任务执行时从 read_responses 队列前面取一个 ReadResponse → 如果队列空了,就返回一个默认的“没有新内容、没有退出”的结果。
调用关系:它把真正的异步读取包装进 pin 返回,这是 Rust 异步接口需要的盒装任务。UnifiedExecProcess 在启动或等待唤醒后读取远程进程状态时,会走到这个接口;早退测试正是靠它读到 exited=true 和退出码。
调用图:外部调用 2 个(pin, new)。
MockExecProcess::write76–78 ↗
fn write(&self, _chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>
作用:模拟向远程进程的标准输入写数据。这里不真的保存数据,只按测试预设回答“接受了”“进程不存在”或“输入已关闭”。
数据流:进去的是一段要写入的字节 → 它忽略具体内容,复制一份预设的 write_response → 出来的是一个异步结果,告诉调用方这次写入在远程端看来是什么状态。
调用关系:UnifiedExecProcess 的 write 方法会通过 ExecProcess 接口调用它。两个写入失败测试分别把返回状态设成 UnknownProcess 和 StdinClosed,用来确认 UnifiedExecProcess 会把进程标记为已退出。
调用图:外部调用 2 个(pin, clone)。
MockExecProcess::signal80–82 ↗
fn signal(&self, _signal: ProcessSignal) -> ExecProcessFuture<'_, ()>
作用:模拟给进程发信号,比如中断或终止信号。这个测试里信号行为不是重点,所以它总是假装成功。
数据流:进去的是一个 ProcessSignal,也就是要发给进程的信号类型 → 它不检查也不记录这个信号 → 出来的是一个成功完成的异步结果,不改变假进程状态。
调用关系:它补齐 ExecProcess 接口,让 MockExecProcess 可以被当作完整的远程进程使用。当前这些测试没有围绕信号展开,所以它只是一个安静的占位实现。
调用图:外部调用 1 个(pin)。
MockExecProcess::terminate84–86 ↗
fn terminate(&self) -> ExecProcessFuture<'_, ()>
作用:模拟请求远程进程结束。测试可以预设它成功,也可以预设它返回协议错误,用来检查 UnifiedExecProcess 是否只在真正成功时更新退出状态。
数据流:进去的是终止请求 → 它返回一个异步任务;任务执行时查看 terminate_error → 如果有错误文本,就变成 ExecServerError::Protocol 返回;如果没有,就返回成功。假进程本身不直接改状态,状态更新由 UnifiedExecProcess 决定。
调用关系:remote_terminate_confirmed_updates_state_on_success_only 通过 remote_process 创建两种假进程:一种终止会失败,一种会成功。UnifiedExecProcess 的 terminate_confirmed 会走到这里,并根据结果决定是否把自己标记为已退出。
调用图:外部调用 2 个(pin, Protocol)。
remote_process89–109 ↗
async fn remote_process(
write_status: WriteStatus,
terminate_error: Option<String>,
) -> UnifiedExecProcess
作用:快速造出一个包着假远程进程的 UnifiedExecProcess。多个测试都用它来少写重复搭建代码。
数据流:进去的是想要的写入返回状态,以及可选的终止错误文本 → 它创建唤醒通道、构造 MockExecProcess、再放进 StartedExecProcess → 最后调用 UnifiedExecProcess::from_exec_server_started,出来的是已经启动好的 UnifiedExecProcess。
调用关系:它是本测试文件的小工厂函数。remote_write_unknown_process_marks_process_exited、remote_write_closed_stdin_marks_process_exited、fail_and_terminate_preserves_failure_message、remote_terminate_confirmed_updates_state_on_success_only 都先用它搭好测试对象,再检查不同场景下的状态变化。
调用图:调用 1 个内部函数(from_exec_server_started);被 4 处调用(fail_and_terminate_preserves_failure_message, remote_terminate_confirmed_updates_state_on_success_only, remote_write_closed_stdin_marks_process_exited, remote_write_unknown_process_marks_process_exited);外部调用 4 个(new, new, new, channel)。
remote_write_unknown_process_marks_process_exited112–122 ↗
async fn remote_write_unknown_process_marks_process_exited()
作用:验证远程端说“这个进程不存在”时,本地会把进程视为已经退出。否则上层可能还会继续往一个不存在的进程写东西。
数据流:开始时创建一个写入状态为 UnknownProcess 的假远程进程 → 对它写入 hello → 写入返回错误,测试再检查错误类型是 WriteToStdin,并检查 process.has_exited() 为真。
调用关系:它调用 remote_process 搭建场景,然后触发 UnifiedExecProcess::write 的失败路径。这个测试保护的是“远程进程丢失”时的本地状态同步逻辑。
调用图:调用 1 个内部函数(remote_process);外部调用 1 个(assert!)。
remote_write_closed_stdin_marks_process_exited125–135 ↗
async fn remote_write_closed_stdin_marks_process_exited()
作用:验证远程端说“标准输入已经关闭”时,本地也会把进程视为已经退出。标准输入就是程序接收键盘或管道输入的入口,关了通常说明不能再写了。
数据流:开始时创建一个写入状态为 StdinClosed 的假远程进程 → 尝试写入 hello → 得到 WriteToStdin 错误 → 最后确认本地进程状态已经变成已退出。
调用关系:它和 UnknownProcess 的测试是一对,都是通过 remote_process 制造写入失败。它确保 UnifiedExecProcess 不会在标准输入关闭后还误判进程仍可交互。
调用图:调用 1 个内部函数(remote_process);外部调用 1 个(assert!)。
fail_and_terminate_preserves_failure_message138–149 ↗
async fn fail_and_terminate_preserves_failure_message()
作用:验证同一个进程被多次标记失败时,第一条失败原因会被保留下来。这样排查问题时看到的是最早、通常也最关键的原因。
数据流:先创建一个正常接受写入的假进程 → 第一次调用 fail_and_terminate 写入“network denied” → 第二次再写入“second failure” → 最后检查进程已退出,并且 failure_message 仍然是第一次的内容。
调用关系:它调用 remote_process 建好对象,然后直接测试 UnifiedExecProcess 的 fail_and_terminate 行为。这里不依赖远程终止是否真的发生,重点是失败信息不能被后续噪音覆盖。
调用图:调用 1 个内部函数(remote_process);外部调用 2 个(assert!, assert_eq!)。
remote_terminate_confirmed_updates_state_on_success_only152–175 ↗
async fn remote_terminate_confirmed_updates_state_on_success_only()
作用:验证“确认终止”这件事必须真的成功,本地才可以把进程标记为退出。否则远程终止失败但本地误以为结束,会造成状态撒谎。
数据流:第一段创建一个终止会报错的假进程 → 调用 terminate_confirmed → 得到 ProcessFailed 类错误,并确认 has_exited 仍然是假。第二段创建一个终止会成功的假进程 → 再调用 terminate_confirmed → 成功后确认 has_exited 变成真。
调用关系:它通过 remote_process 构造失败和成功两条路。调用链会进入 MockExecProcess::terminate,UnifiedExecProcess 根据返回结果决定是否更新自己的退出状态。
调用图:调用 1 个内部函数(remote_process);外部调用 1 个(assert!)。
remote_process_waits_for_early_exit_event178–210 ↗
async fn remote_process_waits_for_early_exit_event()
作用:验证远程进程如果刚启动就已经退出,UnifiedExecProcess 能等到通知并读到退出码。没有这个能力,短命令可能结束太快,启动方反而漏掉结果。
数据流:开始时手动构造一个假进程,它的读取队列里已经放好 exited=true、exit_code=17 的结果 → 另起一个异步任务,短暂等待后通过 wake_tx 发送唤醒通知 → 调用 from_exec_server_started 创建 UnifiedExecProcess → 创建过程读到退出信息,最后测试确认进程已退出且退出码是 17。
调用关系:它不走 remote_process,因为需要精细安排读取队列和唤醒时间。它直接调用 UnifiedExecProcess::from_exec_server_started,并配合 MockExecProcess::subscribe_wake 和 MockExecProcess::read,检查启动阶段能捕捉到很早发生的退出事件。
调用图:调用 1 个内部函数(from_exec_server_started);外部调用 10 个(new, from_millis, new, new, from, assert!, assert_eq!, spawn, sleep, channel)。
core/src/unified_exec/mod_tests.rs源码 ↗
这份文件像一套“验收清单”。统一执行功能允许系统启动一个命令,短命令结束后直接返回结果,长时间运行的交互式命令则留在后台,之后还能继续往里写输入。这里的测试会真的创建会话和命令请求,运行 bash,收集输出,并检查进程编号、退出码、后台终端列表是否符合预期。文件里还有几个测试用的小工具:比如 TestSpawnLifecycle 用来假装有继承的文件描述符,BlockingTerminateExecProcess 用来制造“终止操作卡住”的假进程,方便测试竞争情况。所谓竞争情况,就是两件事几乎同时发生时,比如一边轮询输出一边终止进程,系统不能乱掉。整体上,它保护的是命令执行这条主链路:启动、输出、保留后台、写入 stdin、超时、清理、远程执行,都要表现稳定。
test_session_and_turn37–40 ↗
async fn test_session_and_turn() -> (Arc<Session>, Arc<TurnContext>)
作用:创建一个测试用的会话和一次对话上下文。测试要运行命令时,先需要有一套像真实运行时一样的“外壳环境”。
数据流:进去没有参数 → 它调用测试辅助函数造出 Session 和 TurnContext,并用 Arc(一种可安全共享的引用)包起来 → 出来是一对可被多个异步任务共用的测试会话和上下文。
调用关系:很多测试一开始都会调用它,比如持久终端、超时、终止流程等测试。它把底层的 make_session_and_context 包装成更适合这些异步测试共用的形式。
调用图:调用 1 个内部函数(make_session_and_context);被 9 处调用(completed_commands_do_not_persist_sessions, multi_unified_exec_sessions, requests_with_large_timeout_are_capped, reusing_completed_process_returns_unknown_process, terminating_during_stdin_poll_returns_exited_response, terminating_initial_exec_command_rechecks_initial_response_state, unified_exec_pause_blocks_yield_timeout, unified_exec_persists_across_requests, unified_exec_timeouts);外部调用 1 个(new)。
exec_command42–58 ↗
async fn exec_command(
session: &Arc<Session>,
turn: &Arc<TurnContext>,
cmd: &str,
yield_time_ms: u64,
workdir: Option<PathBuf>,
) -> Result<ExecCommandToolOutput, UnifiedExecError
作用:用最常见的方式执行一条测试命令。它默认带终端模式,省得每个测试重复写同样的启动代码。
数据流:进去是会话、上下文、命令字符串、等待输出的时间、可选工作目录 → 它把这些原样交给 exec_command_with_tty,并固定 tty 为 true → 出来是命令执行工具的输出,或者执行错误。
调用关系:这是多数测试启动命令的便捷入口。真正复杂的创建请求、打开进程、收集输出,都交给 exec_command_with_tty。
调用图:调用 1 个内部函数(exec_command_with_tty);被 7 处调用(completed_commands_do_not_persist_sessions, multi_unified_exec_sessions, requests_with_large_timeout_are_capped, reusing_completed_process_returns_unknown_process, unified_exec_pause_blocks_yield_timeout, unified_exec_persists_across_requests, unified_exec_timeouts)。
shell_env60–62 ↗
fn shell_env() -> HashMap<String, String>
作用:拿到当前测试进程的环境变量。这样被测试启动的 shell 能继承一套真实环境,不至于像在真空里运行。
数据流:进去没有参数 → 它读取操作系统当前环境变量 → 出来是一个字符串到字符串的表,供命令请求使用。
调用关系:exec_command_with_tty 和几个直接构造执行请求的远程/退出码测试会用它。它是 test_exec_request 的输入来源之一。
调用图:被 4 处调用(completed_pipe_commands_preserve_exit_code, exec_command_with_tty, remote_exec_server_rejects_inherited_fd_launches, unified_exec_uses_remote_exec_server_when_configured);外部调用 1 个(vars)。
test_exec_request64–88 ↗
fn test_exec_request(
turn: &TurnContext,
command: Vec<String>,
cwd: AbsolutePathBuf,
env: HashMap<String, String>,
) -> ExecRequest
作用:把一堆零散信息组装成一次“执行命令请求”。这个请求告诉执行系统:跑什么命令、在哪个目录跑、带哪些环境变量、用什么权限和沙箱设置。
数据流:进去是对话上下文、命令数组、工作目录、环境变量 → 它从上下文读取权限配置和工作区根目录,并固定使用默认超时、shell 输出捕获策略、无沙箱 → 出来是 ExecRequest,后续可交给执行管理器启动进程。
调用关系:它被 exec_command_with_tty 以及远程执行、管道退出码等测试调用。它让所有测试用一致的请求格式,避免每个测试自己拼配置。
调用图:调用 2 个内部函数(new, permission_profile);被 4 处调用(completed_pipe_commands_preserve_exit_code, exec_command_with_tty, remote_exec_server_rejects_inherited_fd_launches, unified_exec_uses_remote_exec_server_when_configured)。
exec_command_with_tty90–200 ↗
async fn exec_command_with_tty(
session: &Arc<Session>,
turn: &Arc<TurnContext>,
cmd: &str,
yield_time_ms: u64,
workdir: Option<PathBuf>,
tty: bool,
) -> Result<ExecCommandTool
作用:这是测试里真正启动命令、等待输出、决定是否把进程留下来的核心帮手。它模拟了一次用户调用命令工具的完整过程。
数据流:进去是会话、上下文、命令、等待毫秒数、工作目录、是否使用 tty(伪终端,像真实终端一样交互)→ 它分配进程号,拼出 bash -lc 命令,创建 ExecRequest,打开进程,必要时把仍在运行的进程登记到后台表里,然后在截止时间前收集输出 → 出来是 ExecCommandToolOutput,里面有原始输出、耗时、退出码、可能的后台进程号等;如果命令已经结束,还会释放进程号。
调用关系:exec_command 只是它的简化包装。这个函数直接驱动 UnifiedExecProcessManager,并调用 collect_output_until_deadline 收集输出,是大多数测试场景的共同底座。
调用图:调用 3 个内部函数(new, shell_env, test_exec_request);被 1 处调用(exec_command);外部调用 11 个(clone, downgrade, new, new, from_millis, now, from_utf8_lossy, approx_token_count, collect_output_until_deadline, new (+1 more))。
TestSpawnLifecycle::inherited_fds208–210 ↗
fn inherited_fds(&self) -> Vec<i32>
作用:返回测试里假装要继承给子进程的文件描述符。文件描述符可以理解成系统打开文件或通道时给的编号。
数据流:进去是这个测试对象自身 → 它复制内部保存的 inherited_fds 列表 → 出来是一份文件描述符编号数组,不改动原对象。
调用关系:remote_exec_server_rejects_inherited_fd_launches 会用它制造一种远程执行不支持的启动条件,用来确认系统会拒绝这种请求。
BlockingTerminateExecProcess::process_id246–248 ↗
fn process_id(&self) -> &ProcessId
作用:告诉外部这个假进程的进程编号。测试管理器需要用这个编号识别它。
数据流:进去是这个假进程对象 → 它读取内部保存的 process_id → 出来是这个进程编号的引用,不改变任何状态。
调用关系:这是 ExecProcess 接口的一部分。统一执行进程包装这个假进程时,会通过这个方法拿到身份标识。
BlockingTerminateExecProcess::subscribe_wake250–252 ↗
fn subscribe_wake(&self) -> watch::Receiver<u64>
作用:提供一个“有新动静时叫醒我”的订阅通道。这里用于满足接口,方便测试代码把假进程塞进真实流程。
数据流:进去是这个假进程对象 → 它从 wake_tx 创建一个新的订阅接收端 → 出来是 watch 接收器,外部可以用它等待唤醒信号。
调用关系:这是 ExecProcess 接口要求的方法。BlockingTerminateExecProcess 通过它看起来像一个真正的执行服务进程。
调用图:外部调用 1 个(subscribe)。
BlockingTerminateExecProcess::subscribe_events254–256 ↗
fn subscribe_events(&self) -> ExecProcessEventReceiver
作用:返回一个空的进程事件流。这个假进程不需要真的产生事件,只要能通过接口检查即可。
数据流:进去是这个假进程对象 → 它创建一个空事件接收器 → 出来是不会产生实际事件的 ExecProcessEventReceiver。
调用关系:统一执行包装层可能会订阅进程事件。这里给空实现,重点测试终止卡住时的行为,而不是事件内容。
调用图:调用 1 个内部函数(empty)。
BlockingTerminateExecProcess::read258–265 ↗
fn read(
&self,
_after_seq: Option<u64>,
_max_bytes: Option<usize>,
_wait_ms: Option<u64>,
) -> ExecProcessFuture<'_, ReadResponse>
作用:实现接口里的读取输出方法,但这个假进程永远读不到输出,也不会报告退出。它是为了让测试专注在终止流程上。
数据流:进去是读取参数,但这个实现不使用它们 → 它创建一个异步任务,返回空输出、下一个序号、未退出、未关闭的状态 → 出来是 ReadResponse。
调用关系:write_stdin 或轮询流程可能会读这个假进程的输出。它把读取行为固定住,让 terminating_during_stdin_poll_returns_exited_response 这类测试更可控。
调用图:外部调用 2 个(pin, new)。
BlockingTerminateExecProcess::write267–269 ↗
fn write(&self, _chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>
作用:实现接口里的写入方法,并总是假装写入成功。这样测试可以顺利进入后续等待或终止流程。
数据流:进去是一段要写给进程的字节,但这里不真正保存或处理 → 它返回 Accepted,表示输入被接受 → 出来是 WriteResponse。
调用关系:这是 ExecProcess 接口的一部分。测试通过它让假进程能被统一执行管理器当成正常进程操作。
调用图:外部调用 1 个(pin)。
BlockingTerminateExecProcess::signal271–273 ↗
fn signal(&self, _signal: ProcessSignal) -> ExecProcessFuture<'_, ()>
作用:实现发送信号的方法,但什么也不做并直接成功。信号可以理解成操作系统发给进程的控制消息,比如中断。
数据流:进去是一个进程信号 → 它忽略这个信号,立即返回成功 → 出来没有数据,也不改变假进程状态。
调用关系:统一执行接口要求进程能接收信号。这里的空实现让测试不用关心信号细节,只测试终止和清理。
调用图:外部调用 1 个(pin)。
BlockingTerminateExecProcess::terminate275–277 ↗
fn terminate(&self) -> ExecProcessFuture<'_, ()>
作用:实现“终止进程”,但会故意卡住,直到测试明确放行。这样可以模拟终止操作进行到一半时,系统里其他状态发生变化的情况。
数据流:进去是这个假进程对象 → 它先通知外部“终止已经开始”,然后等待 allow_terminate 通知 → 收到通知后返回成功;期间会改变 terminate_started 这个观察标记。
调用关系:terminating_initial_exec_command_rechecks_initial_response_state 和 terminating_during_stdin_poll_returns_exited_response 依赖这个可控卡点,测试进程终止和输出轮询同时发生时是否清理正确。
调用图:外部调用 2 个(pin, send)。
blocking_terminate_unified_process280–300 ↗
async fn blocking_terminate_unified_process(
process_id: i32,
terminate_started: watch::Sender<bool>,
allow_terminate: Arc<Notify>,
) -> anyhow::Result<Arc<UnifiedExecProcess>>
作用:创建一个包装好的假统一执行进程,它的终止动作会被测试卡住。这样测试不用真的启动系统进程,也能检查复杂的终止时序。
数据流:进去是进程号、一个用于通知终止已开始的通道、一个用于放行终止的通知器 → 它创建唤醒通道,构造 BlockingTerminateExecProcess,再把它包装成 UnifiedExecProcess → 出来是可放进进程表的统一执行进程。
调用关系:两个终止相关测试会调用它。它把假的 ExecProcess 接到真实 UnifiedExecProcess 包装层上,让后续流程尽量接近真实运行。
调用图:调用 1 个内部函数(from_exec_server_started);被 2 处调用(terminating_during_stdin_poll_returns_exited_response, terminating_initial_exec_command_rechecks_initial_response_state);外部调用 2 个(new, channel)。
write_stdin302–319 ↗
async fn write_stdin(
session: &Arc<Session>,
process_id: i32,
input: &str,
yield_time_ms: u64,
) -> Result<ExecCommandToolOutput, UnifiedExecError>
作用:往一个已经存在的后台进程里写输入。对交互式 shell 来说,这就像用户在终端里继续敲命令。
数据流:进去是会话、进程号、输入字符串、等待输出的时间 → 它组装 WriteStdinRequest,设置输出截断策略和最大等待时间,然后交给 unified_exec_manager.write_stdin → 出来是本次写入后收集到的命令输出,或者错误。
调用关系:多个测试用它验证后台 shell 是否保留状态、超时后是否还能继续取输出、已退出进程是否会报未知进程。它是测试“继续和后台进程说话”的主要入口。
调用图:被 5 处调用(multi_unified_exec_sessions, reusing_completed_process_returns_unknown_process, terminating_during_stdin_poll_returns_exited_response, unified_exec_persists_across_requests, unified_exec_timeouts);外部调用 1 个(Tokens)。
push_chunk_preserves_prefix_and_suffix322–341 ↗
fn push_chunk_preserves_prefix_and_suffix()
作用:检查输出缓冲区在内容太多时,仍然保留开头和结尾。这样用户既能看到命令一开始发生了什么,也能看到最后结果。
数据流:进去没有外部输入 → 它创建 HeadTailBuffer,塞入一大段 a,再塞入 b 和 c → 最后断言保留字节数没超限,并且快照里还能找到开头 a、中间保留下来的 b、结尾 c。
调用关系:这是 HeadTailBuffer 的直接单元测试。它不启动真实命令,只验证统一执行输出截断所依赖的缓冲策略。
调用图:调用 1 个内部函数(default);外部调用 3 个(assert!, assert_eq!, vec!)。
head_tail_buffer_default_preserves_prefix_and_suffix344–352 ↗
fn head_tail_buffer_default_preserves_prefix_and_suffix()
作用:检查默认的输出缓冲区转成字节时,也会保留最前面和最后面的内容。
数据流:进去没有外部输入 → 它创建默认 HeadTailBuffer,放入一大段 a 和结尾 bc → 转成完整字节后,断言第一个字节仍是 a,最后仍以 bc 结尾。
调用关系:它补充验证 HeadTailBuffer 的默认行为。统一执行收集大量输出时会依赖这种“头尾都保留”的规则。
调用图:调用 1 个内部函数(default);外部调用 3 个(assert!, assert_eq!, vec!)。
unified_exec_persists_across_requests355–404 ↗
async fn unified_exec_persists_across_requests() -> anyhow::Result<()>
作用:验证交互式命令会在多次请求之间保留下来。也就是说,打开一个 bash 后,后面还能继续往同一个 bash 里输入命令。
数据流:进去没有外部参数 → 它创建测试会话,启动 bash -i,拿到后台进程号,写入环境变量,再写 echo 读取它 → 结果应包含之前设置的值;最后终止后台终端,并确认列表清空。
调用关系:它调用 test_session_and_turn、exec_command 和 write_stdin,覆盖统一执行最核心的后台会话能力。
调用图:调用 3 个内部函数(exec_command, test_session_and_turn, write_stdin);外部调用 3 个(assert!, assert_eq!, skip_if_sandbox!)。
multi_unified_exec_sessions407–461 ↗
async fn multi_unified_exec_sessions() -> anyhow::Result<()>
作用:验证后台交互式 shell 和普通短命令互不污染。一个 shell 里设置的变量,不应该泄漏到另一次新启动的短命令里。
数据流:进去没有外部参数 → 它先打开一个后台 bash 并设置变量,再单独执行一条短命令读取变量,确认读不到;随后回到原后台 bash 再读取,确认能读到 → 输出证明后台 shell 保持状态,而新命令是干净的。
调用关系:它通过 exec_command 启动新命令,通过 write_stdin 操作已有后台进程,检查统一执行管理器是否正确区分不同进程。
调用图:调用 3 个内部函数(exec_command, test_session_and_turn, write_stdin);外部调用 4 个(from_secs, assert!, skip_if_sandbox!, sleep)。
unified_exec_timeouts464–511 ↗
async fn unified_exec_timeouts() -> anyhow::Result<()>
作用:验证等待时间太短时不会硬等到命令完成,但之后还能取回迟到的输出。这个行为对长命令很重要,避免界面卡死。
数据流:进去没有外部参数 → 它打开后台 bash,设置变量,发送一条 sleep 后才输出的命令,并只等 10 毫秒 → 第一次结果不应包含输出;等待几秒后再次轮询 → 第二次结果应包含之前迟到的输出。
调用关系:它使用 exec_command 打开 shell,再用 write_stdin 做短等待和后续轮询,验证输出收集和后台保留配合正常。
调用图:调用 3 个内部函数(exec_command, test_session_and_turn, write_stdin);外部调用 5 个(from_secs, assert!, format!, skip_if_sandbox!, sleep)。
unified_exec_pause_blocks_yield_timeout514–552 ↗
async fn unified_exec_pause_blocks_yield_timeout() -> anyhow::Result<()>
作用:验证系统处于“暂停询问用户”的状态时,命令的让出超时会被暂停计算。简单说,暂停期间不应该因为计时到了就过早返回。
数据流:进去没有外部参数 → 它创建会话并设置暂停状态,另起任务两秒后解除暂停,然后执行一条一秒后输出的命令且等待时间只有 250 毫秒 → 返回时应至少过了两秒,并且包含命令输出,且不留下后台进程。
调用关系:它调用 exec_command,而 exec_command_with_tty 会把会话的暂停状态传给输出收集器。这个测试确认 collect_output_until_deadline 尊重暂停状态。
调用图:调用 2 个内部函数(exec_command, test_session_and_turn);外部调用 7 个(clone, from_secs, assert!, skip_if_sandbox!, spawn, now, sleep)。
requests_with_large_timeout_are_capped556–576 ↗
async fn requests_with_large_timeout_are_capped() -> anyhow::Result<()>
作用:验证很大的等待时间会被系统限制住,避免一次请求占用太久。这个测试当前被忽略,说明还没有稳定的测试方式。
数据流:进去没有外部参数 → 它创建会话,执行 echo codex,并给一个很大的等待时间 → 预期结果包含 codex,并报告一个进程号。
调用关系:它通过 exec_command 走常规执行路径,但标记为 ignore,不会在普通测试运行中执行。
调用图:调用 2 个内部函数(exec_command, test_session_and_turn);外部调用 1 个(assert!)。
completed_commands_do_not_persist_sessions580–613 ↗
async fn completed_commands_do_not_persist_sessions() -> anyhow::Result<()>
作用:验证已经完成的命令不会继续留在后台进程表里。否则系统会以为还有终端活着,造成资源泄漏。
数据流:进去没有外部参数 → 它创建会话,运行 echo codex,检查输出,然后查看进程存储表 → 预期表为空,说明完成的命令被清理掉。
调用关系:它调用 exec_command 走完整执行流程,但当前也被 ignore。它关注的是执行结束后的清理规则。
调用图:调用 2 个内部函数(exec_command, test_session_and_turn);外部调用 1 个(assert!)。
reusing_completed_process_returns_unknown_process616–654 ↗
async fn reusing_completed_process_returns_unknown_process() -> anyhow::Result<()>
作用:验证对已经退出并清理的进程继续写入时,会得到“未知进程号”错误,而不是静默失败或崩溃。
数据流:进去没有外部参数 → 它打开交互式 bash,写入 exit 让它退出,稍等后再对同一进程号写空输入 → 结果应是 UnknownProcessId,并且进程表为空。
调用关系:它用 exec_command 创建后台进程,用 write_stdin 触发退出和再次写入,检查统一执行管理器的错误处理和清理是否一致。
调用图:调用 3 个内部函数(exec_command, test_session_and_turn, write_stdin);外部调用 6 个(from_millis, assert!, assert_eq!, panic!, skip_if_sandbox!, sleep)。
terminating_initial_exec_command_rechecks_initial_response_state657–726 ↗
async fn terminating_initial_exec_command_rechecks_initial_response_state() -> anyhow::Result<()>
作用:验证终止后台终端时,如果最初那次命令响应还没结束,系统会重新检查状态,避免过早移除或遗漏清理。
数据流:进去没有外部参数 → 它创建一个终止会卡住的假进程,手动放进进程表,并标记初始命令仍活跃;随后启动终止任务,等终止真正开始后,把初始活跃标记改为 false,再放行终止 → 最后应返回终止成功,并从进程表移除。
调用关系:它调用 blocking_terminate_unified_process 构造可控假进程,再通过 session.terminate_background_terminal 进入真实终止路径,专门测试一个容易出错的并发时序。
调用图:调用 2 个内部函数(blocking_terminate_unified_process, test_session_and_turn);外部调用 11 个(clone, downgrade, new, from_secs, now, new, assert!, new, spawn, timeout (+1 more))。
terminating_during_stdin_poll_returns_exited_response729–796 ↗
async fn terminating_during_stdin_poll_returns_exited_response() -> anyhow::Result<()>
作用:验证一边等待 stdin 写入后的输出,一边终止进程时,轮询请求会正常结束,并返回进程已不再保留的结果。
数据流:进去没有外部参数 → 它创建一个终止会卡住的假进程并放进进程表,启动一个长时间 write_stdin 轮询任务;等确认轮询已经接触到进程后,释放进程号并放行终止 → 轮询应很快结束,输出里没有后台进程号,进程表为空。
调用关系:它结合 blocking_terminate_unified_process 和 write_stdin,模拟轮询与终止交错发生的情况,确认统一执行管理器不会挂住。
调用图:调用 3 个内部函数(blocking_terminate_unified_process, test_session_and_turn, write_stdin);外部调用 14 个(clone, downgrade, new, from_millis, from_secs, now, new, assert!, assert_eq!, new (+4 more))。
completed_pipe_commands_preserve_exit_code799–834 ↗
async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()>
作用:验证非交互式命令即使很快结束,也能保留正确退出码。退出码是程序告诉外界自己成功还是失败的数字。
数据流:进去没有外部参数 → 它构造一条 bash -lc 'exit 17' 的请求,并用非 tty 模式启动 → 等待进程退出后,检查 has_exited 为真,exit_code 正好是 17。
调用关系:它直接使用 UnifiedExecProcessManager.open_session_with_exec_env,不通过 exec_command 包装,专门检查底层进程包装对退出码的保存。
调用图:调用 5 个内部函数(make_session_and_context, default, shell_env, test_exec_request, default_for_tests);外部调用 4 个(new, assert!, assert_eq!, vec!)。
unified_exec_uses_remote_exec_server_when_configured837–886 ↗
async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Result<()>
作用:验证配置了远程执行环境时,统一执行真的会通过远程执行服务运行命令。远程执行服务就是把命令放到另一套环境里跑,而不是本机直接跑。
数据流:进去没有外部参数 → 如果没有远程测试环境就直接跳过;否则创建远程环境和命令请求,启动远程 bash,写入 printf 命令,再收集输出 → 输出应包含 remote-unified-exec。
调用关系:它调用 test_env 准备远程环境,使用 test_exec_request 构造请求,再直接操作 UnifiedExecProcessManager 和进程输出句柄,验证远程路径可用。
调用图:调用 5 个内部函数(make_session_and_context, default, shell_env, test_exec_request, test_env);外部调用 9 个(new, from_millis, now, assert!, collect_output_until_deadline, get_remote_test_env, skip_if_sandbox!, sleep, vec!)。
remote_exec_server_rejects_inherited_fd_launches889–932 ↗
async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()>
作用:验证远程执行服务会拒绝带继承文件描述符的启动请求。因为远程机器不能简单继承本机已经打开的文件或通道编号。
数据流:进去没有外部参数 → 如果没有远程测试环境就跳过;否则把当前 turn 的环境换成远程环境,构造命令请求,并传入 TestSpawnLifecycle,让它声明要继承 fd 42 → 启动应失败,错误信息应明确说明远程 exec-server 不支持继承文件描述符。
调用关系:它使用 TestSpawnLifecycle::inherited_fds 制造特殊条件,并直接调用 UnifiedExecProcessManager.open_session_with_exec_env,确认远程执行入口会做正确限制。
调用图:调用 5 个内部函数(make_session_and_context, default, shell_env, test_exec_request, test_env);外部调用 6 个(new, new, assert_eq!, get_remote_test_env, skip_if_sandbox!, vec!)。