Codex 系统手册

codex-exec 二进制验证

stage-23.3.322 个文件

这一阶段不是程序正式干活,而是给 codex-exec 这个命令行工具做“出厂检查”。它从用户最外层能碰到的地方下手:先测终端参数会不会读错,尤其是 resume、目录、配置和提示词;再测服务端事件能不能正确翻成人看的文字、JSON 或 JSONL;还会检查会话能否保存和恢复,标准输入、AGENTS.md、输出格式要求有没有带上;最后盯住认证头、审批策略、MCP 启动失败、hooks、服务器报错退出码,以及打补丁改文件流程,确保这些零件合在一起时,用户看到的行为稳定可靠。

本阶段的文件22

CLI 形态与启动辅助

这些测试固定参数解析和库级启动行为,后者会在任何输出或子进程执行发生前准备 exec 运行。

exec/src/cli_tests.rs源码 ↗
testtest run

这个文件不是真正运行产品功能的代码,而是给命令行入口做“验收检查”。命令行工具最怕的一类问题是:用户明明输入了一个提示词、输出文件或配置开关,程序却把它当成了别的东西。这里的测试就像售票口的试题,检查售票员会不会把“目的地”“优惠券”“备注”分错格子。它会模拟用户输入 codex-exec ...,调用 Cli::parse_from 把一串文字解析成程序内部的结构,然后检查关键字段是否符合预期。重点覆盖了 resume 命令后面跟提示词、输出文件参数、忽略用户配置和规则的隔离开关,以及已经废弃的 --full-auto 参数是否给出正确迁移提示。没有这些测试,改命令行解析规则时很容易悄悄破坏老用法,用户只会在实际运行时才发现。

函数细节4
resume_parses_prompt_after_global_flags5–36 ↗
fn resume_parses_prompt_after_global_flags()

作用:这个测试确认:在 resume 子命令后面放了很多全局开关之后,最后那个真正的提示词仍然能被正确认出来。它防止程序把提示词误当成会话编号或某个开关的参数。

数据流:进去的是一组假装从终端输入的字符串,包括 resume--last、模型名、跳过检查、忽略配置等开关,以及最后的提示词。测试把这些字符串交给 Cli::parse_from 解析,然后检查解析结果里的隔离开关确实打开了,再从 Resume 命令参数里推导出最终提示词。出来的结果不是业务输出,而是断言通过;如果提示词或开关被解析错,测试会失败。

调用关系:它在测试运行时直接调用命令行解析器 Cli::parse_from,然后用断言检查结果。它不继续执行真正的 resume 功能,只负责守住“参数被分到正确位置”这一关;如果解析出来的命令不是 Command::Resume,它会直接报错。

调用图:外部调用 4 个(parse_from, assert!, assert_eq!, panic!)。

resume_accepts_output_flags_after_subcommand39–62 ↗
fn resume_accepts_output_flags_after_subcommand()

作用:这个测试确认:resume 子命令后面仍然可以写输出相关参数,比如把最后一条消息保存到文件、指定输出格式说明文件。它保证用户不必把所有全局输出选项都挤在子命令前面。

数据流:进去的是一组模拟命令行参数:程序名、resume、会话编号、输出文件路径、输出 schema 路径,以及新的提示词。测试解析它们后,检查顶层配置里记录了正确的输出文件和 schema 文件,再检查 Resume 参数里保存了正确的会话编号和提示词。最后出来的是一组通过的断言;如果某个路径被漏掉,或提示词被吃掉,测试就会失败。

调用关系:它同样只和命令行解析层互动:先把字符串交给 Cli::parse_from,再检查解析后的 CliCommand::Resume。它的作用是补住一个常见使用场景:用户在子命令之后继续补输出选项时,解析器也要认得。

调用图:外部调用 3 个(parse_from, assert_eq!, panic!)。

parses_config_isolation_flags65–75 ↗
fn parses_config_isolation_flags()

作用:这个测试确认两个“隔离配置”的开关能被正确识别:忽略用户配置、忽略规则。这样程序在需要干净环境运行时,不会偷偷读到用户本地设置。

数据流:进去的是一组很短的模拟命令行参数:程序名、--ignore-user-config--ignore-rules,以及一个普通提示词 summarize。测试把它们解析成 Cli,然后检查两个布尔开关都变成了真。出来的是测试通过或失败;它不会真的读取或跳过配置文件,只检查“开关有没有被记下来”。

调用关系:它调用 Cli::parse_from 做解析,然后用断言确认结果。它服务于更大的启动流程:真正运行时,后续配置加载代码会根据这些字段决定要不要读取用户配置和规则;这个测试确保前面这一步没有掉链子。

调用图:外部调用 2 个(parse_from, assert!)。

removed_full_auto_flag_reports_migration_path78–85 ↗
fn removed_full_auto_flag_reports_migration_path()

作用:这个测试确认旧的 --full-auto 参数虽然已经废弃,但程序会给用户一个清楚的替代用法提示。它避免用户升级后只看到失败或沉默,而不知道该怎么改命令。

数据流:进去的是模拟命令行 codex-exec --full-auto summarize。测试先解析参数,再调用 removed_full_auto_warning() 取得废弃提示信息,然后和预期文字比较。出来的是一个通过的断言;如果提示消失了或文案不对,测试会失败。

调用关系:它位于命令行兼容性检查这一层:Cli::parse_from 先保留用户输入过旧参数的信息,随后 removed_full_auto_warning() 把这个信息转换成面向用户的警告。这个测试确保旧参数到新参数的迁移路径一直能被清楚告知。

调用图:外部调用 2 个(parse_from, assert_eq!)。

exec/src/main_tests.rs源码 ↗
testtest run

这个文件只有一个测试,用来防止一个很具体但很容易出错的问题:命令行里参数的顺序很复杂时,程序还能不能正确理解用户想干什么。它模拟用户运行 codex-exec resume ...,里面既有恢复会话用的选项,比如 --last,也有全局配置,比如 --config reasoning_level=xhigh,最后还有真正要执行的提示词。测试会先让命令行解析器把这些文字拆成程序内部能懂的结构,再检查三件事:第一,最终提示词还是用户最后输入的那句话;第二,配置覆盖项没有丢;第三,严格配置模式确实打开了。它像是在收银台故意递一张很复杂的小票,确认机器不会把商品、优惠券和备注混在一起。

函数细节1
top_cli_parses_resume_prompt_after_config_flag5–43 ↗
fn top_cli_parses_resume_prompt_after_config_flag()

作用:这个测试确认 resume 命令后面跟着配置参数时,最后的提示词仍然会被正确当作提示词,而不是被误当成配置或会话编号。有人改命令行解析规则时,它能及时发现解析被改坏了。

数据流:进去的是一串模拟的命令行文字,就像用户在终端里输入的一整行命令。测试把它交给 TopCli::parse_from 解析成内部结构,然后把外层的配置覆盖合并到内层命令里,再取出 resume 命令的参数。出来的是一组断言结果:提示词必须等于预期字符串,配置覆盖列表里必须有一条 reasoning_level=xhigh,并且严格配置开关必须为真;如果不符合,测试就失败。

调用关系:它在测试运行时由 Rust 的测试框架自动调用。它主要把工作交给命令行解析器 parse_from,随后用 assert_eq!assert! 检查解析结果;如果解析出来的不是 resume 命令,就用 panic! 直接说明测试前提已经不成立。

调用图:外部调用 4 个(assert!, assert_eq!, parse_from, panic!)。

exec/src/lib_tests.rs源码 ↗
testtest

这个文件不提供正式功能,而是专门“验货”。它把 exec 模块里一些容易出错的边界情况单独拎出来测:默认是否开启分析,上报追踪时能不能接上外部 trace,上屏日志会不会被 OpenTelemetry(遥测系统,用来收集运行信息)的内部报错刷屏,用户输入文件遇到 UTF-8、UTF-16 或不支持的 UTF-32 编码时怎么处理,审查命令怎么生成请求,以及启动/恢复 app-server 线程时权限、沙箱、人工审批策略是否传对。里面还有一个小的内存日志接收器 TestLogWriter/TestLogSink,像临时录音笔一样把日志写进内存,方便测试检查。整体作用是把“命令行工具和后台服务之间约定好的行为”固定下来,避免配置、安全权限、用户提示这些敏感部分因为重构而变样。

函数细节37
test_tracing_subscriber20–24 ↗
fn test_tracing_subscriber() -> impl tracing::Subscriber + Send + Sync

作用:创建一个测试用的 tracing subscriber(追踪订阅器,可以理解成收集运行轨迹的监听器)。这样测试里生成的 span(一次操作的追踪片段)能接入 OpenTelemetry。

数据流:进去没有业务输入 → 它创建一个假的追踪提供者和 tracer,再把它装到 tracing 的注册表里 → 出来一个可用于测试的 subscriber,不改动真实运行环境。

调用关系:它是追踪测试的准备工具;exec_root_span_can_be_parented_from_trace_context 会先调用它搭好追踪环境,然后再验证根 span 是否能继承外部 trace 信息。

调用图:被 1 处调用(exec_root_span_can_be_parented_from_trace_context);外部调用 3 个(builder, layer, registry)。

exec_defaults_analytics_to_enabled27–29 ↗
fn exec_defaults_analytics_to_enabled()

作用:确认 exec 默认会开启 analytics(分析统计,用来收集使用情况或诊断信息)。这个测试防止默认值被无意改成关闭。

数据流:进去没有输入 → 它读取 DEFAULT_ANALYTICS_ENABLED 这个默认常量 → 用断言确认结果是 true。

调用关系:这是一个最小的默认配置检查,不依赖其他测试辅助函数;它只在测试运行时直接验证常量。

调用图:外部调用 1 个(assert_eq!)。

TestLogWriter::make_writer43–47 ↗
fn make_writer(&'a self) -> Self::Writer

作用:为测试日志系统创建一个实际写日志的对象。它让每次写日志都写进同一块内存缓冲区。

数据流:进去的是 TestLogWriter 里保存的共享 buffer(内存字节数组) → 它复制一份 Arc 引用(共享指针,不复制真正数据) → 出来一个 TestLogSink,后续日志会写到同一个 buffer。

调用关系:它被 tracing_subscriber 的格式化日志层自动调用;exec_default_stderr_filter_suppresses_otel_self_diagnostics 用它来截获日志内容,之后检查哪些日志被过滤掉。

调用图:外部调用 1 个(clone)。

TestLogSink::write51–54 ↗
fn write(&mut self, buf: &[u8]) -> io::Result<usize>

作用:把测试中产生的日志字节写进内存。它代替真正的 stderr,方便测试事后读取日志内容。

数据流:进去是一段要写出的字节 → 它拿到互斥锁(防止多个写入同时改同一块内存),把字节追加到 buffer → 返回写入的字节数,同时 buffer 被更新。

调用关系:它是 TestLogWriter::make_writer 创建出来的写入端;日志层在输出每条日志时会调用它,测试再读取 buffer 判断过滤器是否生效。

TestLogSink::flush56–58 ↗
fn flush(&mut self) -> io::Result<()>

作用:满足写入接口要求的“刷新”动作。因为测试日志已经直接进内存,所以这里不需要真的做什么。

数据流:进去没有额外数据 → 它不修改 buffer,也不触发磁盘或终端输出 → 直接返回成功。

调用关系:它和 TestLogSink::write 一起让 TestLogSink 符合标准 Write 接口;日志系统需要这个接口才能把它当作输出目标。

exec_default_stderr_filter_suppresses_otel_self_diagnostics62–84 ↗
fn exec_default_stderr_filter_suppresses_otel_self_diagnostics()

作用:确认默认 stderr 日志过滤器会屏蔽 OpenTelemetry 自己的内部报错,但不会屏蔽 exec 真正的错误。这样用户不会被遥测系统的噪音误导。

数据流:进去是一个空的内存日志 buffer 和默认日志过滤规则 → 它发出三条 error 日志,两条来自遥测组件,一条来自测试目标 → 最后读取 buffer,确认遥测错误不在里面,而真实 exec 错误还在。

调用关系:这个测试使用 TestLogWriter 和 TestLogSink 截获日志;它验证 EXEC_DEFAULT_LOG_FILTER 这条规则在 tracing subscriber 中的实际效果。

调用图:外部调用 10 个(clone, new, try_new, new, from_utf8, new, assert!, with_default, layer, registry)。

exec_root_span_can_be_parented_from_trace_context87–103 ↗
fn exec_root_span_can_be_parented_from_trace_context()

作用:确认 exec 的根追踪 span 可以接上外部传来的 W3C trace context(跨系统传递追踪编号的标准格式)。这能让一次请求在不同服务之间串成同一条链路。

数据流:进去是一段带 traceparent/tracestate 的测试上下文 → 它创建 exec 根 span,并把外部上下文设置为父级 → 最后读出 span 的 trace id,确认它变成了传入的编号。

调用关系:它先调用 test_tracing_subscriber 搭建测试追踪环境,再调用 set_parent_from_w3c_trace_context 完成父子关系设置,最后用断言检查结果。

调用图:调用 1 个内部函数(test_tracing_subscriber);外部调用 3 个(assert!, assert_eq!, set_default)。

builds_uncommitted_review_request106–122 ↗
fn builds_uncommitted_review_request()

作用:确认当用户要求审查未提交改动时,会生成正确的 review request(审查请求)。

数据流:进去是一组 ReviewArgs,其中 uncommitted 为 true,其他目标为空 → 它调用 build_review_request → 出来应是目标为 UncommittedChanges 的请求。

调用关系:它直接测试 build_review_request 的一种输入分支,保证命令行参数能被翻译成后台理解的审查目标。

调用图:外部调用 1 个(assert_eq!)。

builds_commit_review_request_with_title125–144 ↗
fn builds_commit_review_request_with_title()

作用:确认当用户指定某个 commit 和标题时,审查请求会带上这两个信息。commit 是一次代码提交的编号。

数据流:进去是包含 commit sha 和 commit_title 的 ReviewArgs → 它构造审查请求 → 出来应是 Commit 目标,并保留 sha 和标题。

调用关系:它覆盖 build_review_request 的 commit 分支,确保用户指定的提交信息不会在转换过程中丢失。

调用图:外部调用 1 个(assert_eq!)。

builds_custom_review_request_trims_prompt147–165 ↗
fn builds_custom_review_request_trims_prompt()

作用:确认自定义审查说明会去掉首尾多余空格。这样用户多打的空格不会进入真正的指令。

数据流:进去是带前后空格的 prompt → build_review_request 把它当作自定义 instructions,并做 trim → 出来是没有首尾空格的 Custom 请求。

调用关系:它测试 build_review_request 对自定义说明的清理行为,和前两个审查请求测试一起覆盖主要输入形态。

调用图:外部调用 1 个(assert_eq!)。

decode_prompt_bytes_strips_utf8_bom168–174 ↗
fn decode_prompt_bytes_strips_utf8_bom()

作用:确认读取 UTF-8 文本时,如果开头有 BOM(文件开头的编码标记),会把这个标记去掉。用户最终看到的提示词不应多一个隐藏字符。

数据流:进去是一段以 UTF-8 BOM 开头的字节 → decode_prompt_bytes 识别并跳过 BOM,再按 UTF-8 解码 → 出来是普通字符串 hi 加换行。

调用关系:它测试 decode_prompt_bytes 的 UTF-8 BOM 分支,属于输入文件解码安全网的一部分。

调用图:外部调用 1 个(assert_eq!)。

decode_prompt_bytes_decodes_utf16le_bom177–184 ↗
fn decode_prompt_bytes_decodes_utf16le_bom()

作用:确认带 UTF-16LE BOM 的提示词文件可以被正确读出来。UTF-16LE 是一种每个字符通常用两个字节、小端顺序的文本编码。

数据流:进去是 UTF-16LE BOM 加上 hi 换行的字节 → decode_prompt_bytes 识别编码并转换成 Rust 字符串 → 出来是 hi 加换行。

调用关系:它覆盖 decode_prompt_bytes 的 UTF-16LE 支持,保证来自不同编辑器的提示词文件也能读取。

调用图:外部调用 1 个(assert_eq!)。

decode_prompt_bytes_decodes_utf16be_bom187–194 ↗
fn decode_prompt_bytes_decodes_utf16be_bom()

作用:确认带 UTF-16BE BOM 的提示词文件可以被正确读出来。UTF-16BE 是 UTF-16 的大端字节顺序版本。

数据流:进去是 UTF-16BE BOM 加上 hi 换行的字节 → decode_prompt_bytes 按大端 UTF-16 解码 → 出来是 hi 加换行。

调用关系:它和 UTF-16LE 测试配套,确保 decode_prompt_bytes 不只支持一种 UTF-16 字节顺序。

调用图:外部调用 1 个(assert_eq!)。

decode_prompt_bytes_rejects_utf32le_bom197–212 ↗
fn decode_prompt_bytes_rejects_utf32le_bom()

作用:确认 UTF-32LE 编码会被明确拒绝,而不是误读成乱码。UTF-32 是每个字符用四个字节的编码,这里不支持。

数据流:进去是带 UTF-32LE BOM 的字节 → decode_prompt_bytes 识别出不支持的编码 → 出来是 UnsupportedBom 错误,标明 UTF-32LE。

调用关系:它验证 decode_prompt_bytes 的失败路径,保证不支持的文件格式会给出清楚错误。

调用图:外部调用 1 个(assert_eq!)。

decode_prompt_bytes_rejects_utf32be_bom215–230 ↗
fn decode_prompt_bytes_rejects_utf32be_bom()

作用:确认 UTF-32BE 编码也会被明确拒绝。这样无论 UTF-32 是大端还是小端,都不会被错误处理。

数据流:进去是带 UTF-32BE BOM 的字节 → decode_prompt_bytes 判断编码不支持 → 出来是 UnsupportedBom 错误,标明 UTF-32BE。

调用关系:它和 UTF-32LE 拒绝测试组成一对,覆盖 decode_prompt_bytes 对 UTF-32 的两种常见 BOM。

调用图:外部调用 1 个(assert_eq!)。

decode_prompt_bytes_rejects_invalid_utf8233–240 ↗
fn decode_prompt_bytes_rejects_invalid_utf8()

作用:确认没有 BOM 时,如果内容不是合法 UTF-8,会返回清楚的错误。UTF-8 是最常见的文本编码。

数据流:进去是一段非法 UTF-8 字节 → decode_prompt_bytes 尝试按 UTF-8 解码并失败 → 出来是 InvalidUtf8 错误,并指出从哪里开始无效。

调用关系:它测试 decode_prompt_bytes 的错误报告质量,防止坏输入被悄悄吞掉或变成乱码。

调用图:外部调用 1 个(assert_eq!)。

prompt_with_stdin_context_wraps_stdin_block243–250 ↗
fn prompt_with_stdin_context_wraps_stdin_block()

作用:确认把用户提示词和标准输入内容合并时,会用 <stdin> 标签把输入内容包起来。标准输入就是从管道或终端传进来的额外文本。

数据流:进去是一段用户提示词和一段 stdin 文本 → prompt_with_stdin_context 在中间加空行,并把 stdin 放进 <stdin>...</stdin> → 出来是一段组合后的完整提示词。

调用关系:它直接验证 prompt_with_stdin_context 的格式约定,确保模型能区分用户指令和附加输入内容。

调用图:外部调用 1 个(assert_eq!)。

prompt_with_stdin_context_preserves_trailing_newline253–260 ↗
fn prompt_with_stdin_context_preserves_trailing_newline()

作用:确认 stdin 本身末尾有换行时,合并结果不会多出一层空行。这样提示词格式稳定,不会因为输入结尾不同而变化太大。

数据流:进去是用户提示词和末尾带换行的 stdin → prompt_with_stdin_context 规范化结尾,再包进 <stdin> 标签 → 出来和没有多余末尾换行时一样整齐。

调用关系:它补充测试 prompt_with_stdin_context 的边界情况,保证输入文本收尾处理一致。

调用图:外部调用 1 个(assert_eq!)。

lagged_event_warning_message_is_explicit263–268 ↗
fn lagged_event_warning_message_is_explicit()

作用:确认事件流落后并丢事件时,警告文字说得足够明白。这样用户或开发者能知道不是普通错误,而是内部事件流跟不上了。

数据流:进去是 skipped 数量 7 → lagged_event_warning_message 生成警告字符串 → 出来应明确写着丢了 7 个事件。

调用关系:它测试警告文案函数的固定输出,避免以后改动让提示变得含糊。

调用图:外部调用 1 个(assert_eq!)。

runtime_warnings_are_filtered_to_the_primary_thread271–297 ↗
fn runtime_warnings_are_filtered_to_the_primary_thread()

作用:确认运行时警告只处理全局警告和当前主线程的警告,不处理别的线程的警告。这里的线程是 app-server 里的会话线索,不是操作系统线程。

数据流:进去是三个 WarningNotification:全局、主线程、其他线程 → should_process_notification 分别判断 → 出来是 true、true、false。

调用关系:它验证通知过滤规则,确保 exec 显示给用户的警告只和当前会话相关,避免串台。

调用图:外部调用 1 个(assert_eq!)。

resume_lookup_model_providers_filters_only_last_lookup300–331 ↗
async fn resume_lookup_model_providers_filters_only_last_lookup()

作用:确认恢复最近一次会话时会限制模型提供商,但恢复指定会话时不加这个限制。模型提供商就是实际提供模型服务的一方,比如 openai。

数据流:进去是临时配置、一个 --last 恢复参数和一个指定 session_id 的恢复参数 → resume_lookup_model_providers 根据参数判断 → 对 --last 返回当前 provider 列表,对指定会话返回 None。

调用关系:它用 ConfigBuilder 搭出测试配置,再直接验证 resume_lookup_model_providers 的分支逻辑,避免恢复会话时查错范围。

调用图:外部调用 4 个(assert_eq!, default, tempdir, vec!)。

turn_items_for_thread_returns_matching_turn_items334–397 ↗
fn turn_items_for_thread_returns_matching_turn_items()

作用:确认能从一个线程的多个 turn(一次问答/操作回合)里取出指定 turn 的内容项。

数据流:进去是一个含两个 turn 的线程和一个 turn id → turn_items_for_thread 查找匹配 id → 找到 turn-1 时返回它的消息项,找不到时返回 None。

调用关系:它测试 turn_items_for_thread 这个小查询函数,服务于后续处理某个回合结果或补齐数据的流程。

调用图:外部调用 4 个(new, assert_eq!, test_path_buf, vec!)。

should_backfill_turn_completed_items_skips_ephemeral_threads400–420 ↗
fn should_backfill_turn_completed_items_skips_ephemeral_threads()

作用:确认临时线程不会补填 turn 完成事件里的内容项。临时线程是短命会话,不需要像正式线程那样回补历史内容。

数据流:进去是一个 TurnCompleted 通知和 thread_ephemeral=true → should_backfill_turn_completed_items 判断是否要补填 → 出来是 false。

调用关系:它验证完成通知处理前的一个保护规则,防止临时线程触发不必要的数据补齐。

调用图:外部调用 3 个(TurnCompleted, new, assert!)。

canceled_mcp_server_elicitation_response_uses_cancel_action423–437 ↗
fn canceled_mcp_server_elicitation_response_uses_cancel_action()

作用:确认取消 MCP server elicitation(服务器向用户追问信息的请求)时,返回的是 Cancel 动作。MCP 可以理解为工具/服务和模型之间的一套通信协议。

数据流:进去没有业务输入 → canceled_mcp_server_elicitation_response 生成 JSON 值 → 测试再把 JSON 反序列化,确认 action 是 Cancel,内容和元数据为空。

调用关系:它验证取消响应的协议形状,确保外部 MCP 服务收到的是明确的取消,而不是空回复或格式错误。

调用图:外部调用 2 个(assert_eq!, from_value)。

thread_start_params_include_review_policy_when_review_policy_is_manual_only440–466 ↗
async fn thread_start_params_include_review_policy_when_review_policy_is_manual_only()

作用:确认手动审批模式下,启动线程的参数会带上“由用户审批”的策略。审批策略决定危险操作要不要先问用户。

数据流:进去是一个测试配置,其中 approvals_reviewer 设置为 User → thread_start_params_from_config 把配置转成线程启动参数 → 出来包含 User 审批者、没有单独 sandbox,并带上权限选择。

调用关系:它通过 ConfigBuilder 创建配置,再验证 thread_start_params_from_config 如何把本地配置交给 app-server。

调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 4 个(default, assert_eq!, default, tempdir)。

thread_start_params_include_review_policy_when_auto_review_is_enabled469–489 ↗
async fn thread_start_params_include_review_policy_when_auto_review_is_enabled()

作用:确认启用自动审查时,启动线程参数会带上 AutoReview。自动审查表示某些审批由系统审查器辅助判断。

数据流:进去是 approvals_reviewer 为 AutoReview 的配置 → thread_start_params_from_config 转换参数 → 出来参数里的 approvals_reviewer 也是 AutoReview。

调用关系:它覆盖 thread_start_params_from_config 的另一个审批策略分支,和手动审批测试一起保证策略传递完整。

调用图:外部调用 4 个(default, assert_eq!, default, tempdir)。

build_exec_config_retries_without_invalid_headless_policy_for_auto_review492–550 ↗
async fn build_exec_config_retries_without_invalid_headless_policy_for_auto_review()

作用:确认在自动审查场景下,如果临时塞进去的 headless 审批策略不被系统要求允许,build_exec_config 会去掉它再试一次。headless 这里指无人交互运行时常用的“不要询问”策略。

数据流:进去是带配置文件、requirements 限制和命令行覆盖的临时环境 → 第一次构建因 approval_policy=never 不合规失败 → build_exec_config 在允许重试时去掉这个合成策略再构建,最后得到 OnRequest 和 AutoReview 的配置。

调用关系:它测试 build_exec_config 的容错编排:先让 ConfigBuilder 失败,再确认 exec 层能按设计重试并保留自动审查设置。

调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 5 个(default, assert!, assert_eq!, write, tempdir)。

build_exec_config_preserves_headless_error_when_retry_fails553–575 ↗
async fn build_exec_config_preserves_headless_error_when_retry_fails()

作用:确认如果去掉 headless 策略后的重试也失败,最终报告的是最初的 headless 错误。这样用户看到的错误不会被后续试探性重试掩盖。

数据流:进去是一个总会返回错误的构建闭包:第一次报 headless error,重试报 retry error → build_exec_config 尝试重试但仍失败 → 出来保留原始 headless error。

调用关系:它验证 build_exec_config 的错误选择规则,和上一条重试成功测试一起覆盖成功和失败两条路。

调用图:外部调用 2 个(default, assert_eq!)。

thread_start_params_include_user_thread_source578–594 ↗
async fn thread_start_params_include_user_thread_source()

作用:确认新启动线程时会标明来源是 User。这个来源字段能帮助后台区分线程是用户主动开的,还是系统生成的。

数据流:进去是默认测试配置 → thread_start_params_from_config 生成启动参数 → 出来 thread_source 是 User。

调用关系:它直接检查 thread_start_params_from_config 的默认来源设置,保证 app-server 收到的线程元信息完整。

调用图:外部调用 3 个(assert_eq!, default, tempdir)。

thread_lifecycle_params_preserve_hook_trust_bypass597–620 ↗
async fn thread_lifecycle_params_preserve_hook_trust_bypass()

作用:确认启动和恢复线程时都会保留 bypass_hook_trust 这个配置。它表示绕过 hook 信任检查,是一个敏感开关。

数据流:进去是 bypass_hook_trust=true 的配置 → 分别生成 start 参数和 resume 参数 → 两者出来的 config 字段里都包含 bypass_hook_trust:true。

调用关系:它同时测试 thread_start_params_from_config 和 thread_resume_params_from_config,确保线程生命周期两个入口传递同一份关键配置。

调用图:外部调用 6 个(default, from, assert_eq!, default, Bool, tempdir)。

active_profile_selection_uses_profile_id_only623–629 ↗
fn active_profile_selection_uses_profile_id_only()

作用:确认从活动权限配置里取选择结果时,只使用 profile id。权限配置 profile 可以理解为一套预设的安全规则名字。

数据流:进去是一个 ActivePermissionProfile,id 为内置 workspace profile → permission_profile_id_from_active_profile 提取 id → 出来是这个 id 的字符串。

调用关系:它测试权限配置选择的小转换函数,确保后续传给服务器的是稳定的 profile 标识,而不是整份复杂对象。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile632–661 ↗
async fn thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile()

作用:确认没有活动权限 profile 时,旧式 sandbox(沙箱,限制程序能访问什么的安全边界)设置仍会传给启动和恢复参数。

数据流:进去是设置了 DangerFullAccess sandbox、但没有 active permission profile 的配置 → 生成 start 和 resume 参数 → 两者都带 legacy sandbox,permissions 字段为空。

调用关系:它覆盖新旧权限系统的兼容路径,测试 thread_start_params_from_config 和 thread_resume_params_from_config 在没有新 profile 时不会丢掉旧配置。

调用图:调用 1 个内部函数(without_managed_config_for_tests);外部调用 4 个(default, assert_eq!, default, tempdir)。

session_configured_from_thread_response_uses_review_policy_from_response664–687 ↗
async fn session_configured_from_thread_response_uses_review_policy_from_response()

作用:确认根据线程启动响应生成 SessionConfigured 事件时,审批审查策略以服务器响应为准。

数据流:进去是默认配置和 sample_thread_start_response 生成的响应 → session_configured_from_thread_start_response 组装启动完成事件 → 出来事件里的 session_id、thread_id 和 approvals_reviewer 都符合响应内容。

调用关系:它调用 sample_thread_start_response 准备标准响应,再测试 session_configured_from_thread_start_response 如何把 app-server 的结果转换成协议事件。

调用图:调用 1 个内部函数(sample_thread_start_response);外部调用 3 个(assert_eq!, default, tempdir)。

session_configured_from_thread_response_uses_permission_profile_from_config690–708 ↗
async fn session_configured_from_thread_response_uses_permission_profile_from_config()

作用:确认 SessionConfigured 事件里的权限 profile 来自本地配置的有效权限。也就是说,事件要反映 exec 实际采用的权限设置。

数据流:进去是默认配置和标准线程启动响应 → session_configured_from_thread_start_response 生成事件 → 出来 event.permission_profile 等于 config 计算出的有效权限 profile。

调用关系:它复用 sample_thread_start_response,只关注权限 profile 字段,补充验证会话配置事件的安全信息。

调用图:调用 1 个内部函数(sample_thread_start_response);外部调用 3 个(assert_eq!, default, tempdir)。

session_configured_from_thread_response_preserves_thread_source711–729 ↗
async fn session_configured_from_thread_response_preserves_thread_source()

作用:确认线程来源字段会从服务器响应保留下来,并写入 SessionConfigured 事件。

数据流:进去是默认配置和带 thread_source=User 的线程响应 → 转成 SessionConfigured 事件 → 出来 thread_source 仍然是 User。

调用关系:它调用 sample_thread_start_response 提供带来源的响应,检查 session_configured_from_thread_start_response 不会丢掉这个元信息。

调用图:调用 1 个内部函数(sample_thread_start_response);外部调用 3 个(assert_eq!, default, tempdir)。

session_configured_from_thread_response_preserves_parent_thread_id732–749 ↗
async fn session_configured_from_thread_response_preserves_parent_thread_id()

作用:确认如果线程响应里有 parent_thread_id(父线程编号),生成的会话配置事件也会保留它。这个字段用于表示线程之间的继承或分叉关系。

数据流:进去是默认配置、一个新生成的 parent_thread_id,以及被修改过的标准响应 → session_configured_from_thread_start_response 解析并复制这个父线程 id → 出来事件里 parent_thread_id 相同。

调用关系:它先调用 sample_thread_start_response 得到模板响应,再手动加上父线程编号,用来测试转换函数对线程关系字段的保真度。

调用图:调用 2 个内部函数(sample_thread_start_response, new);外部调用 3 个(assert_eq!, default, tempdir)。

sample_thread_start_response751–792 ↗
fn sample_thread_start_response() -> ThreadStartResponse

作用:生成一个固定的线程启动响应样本,供多个测试复用。这样测试不用每次手写一大坨重复数据。

数据流:进去没有输入 → 它填好线程 id、session id、路径、模型、审批策略、沙箱、来源等字段 → 出来一个 ThreadStartResponse 测试对象。

调用关系:它是几个 session_configured_from_thread_response_* 测试的共同夹具(fixture,也就是测试样板数据),帮助这些测试专注检查不同字段的转换结果。

调用图:被 4 处调用(session_configured_from_thread_response_preserves_parent_thread_id, session_configured_from_thread_response_preserves_thread_source, session_configured_from_thread_response_uses_permission_profile_from_config, session_configured_from_thread_response_uses_review_policy_from_response);外部调用 5 个(from, new, new, test_path_buf, vec!)。

输出事件处理器

这些套件验证 exec 如何将内部/app-server 事件转换为人类可读、JSONL 和机器可读的输出流,包括完成和失败处理。

exec/src/event_processor_with_human_output_tests.rs源码 ↗
testtest run

这个文件不参与正式运行,而是在开发和发布前当“验收清单”用。它测试 EventProcessorWithHumanOutput 这个输出处理器:什么时候把最终回答打印到标准输出,什么时候只在终端里显示;推理内容是显示摘要还是原文;权限配置要怎么用一句话讲给用户听;一轮对话结束、失败、被打断时,缓存的最终答案该保留还是清掉。可以把它想成电影院放映前的检查:字幕、声音、出口提示都要对。这里的“标准输出”就是程序给其他程序或文件用的输出通道,“终端”是人直接看的屏幕。测试里还会临时造配置、临时目录和假的服务端通知,模拟真实运行时会发生的情况。

函数细节20
suppresses_final_stdout_message_when_both_streams_are_terminals31–37 ↗
fn suppresses_final_stdout_message_when_both_streams_are_terminals()

作用:检查当标准输出和标准错误都连着真人终端时,最终回答不要再额外写到标准输出。这样可以避免同一段答案在屏幕上重复出现。

数据流:进去的是一段最终消息和两个“都是终端”的标记 → 测试调用判断逻辑,看它是否决定不写标准输出 → 出来的是断言通过,说明结果是“不要打印”。

调用关系:这是围绕最终消息输出规则的一个边界测试。它直接用断言检查被测判断函数的结果,不再把工作交给其他测试代码。

调用图:外部调用 1 个(assert!)。

prints_final_stdout_message_when_stdout_is_not_terminal40–46 ↗
fn prints_final_stdout_message_when_stdout_is_not_terminal()

作用:检查标准输出不是终端时,最终回答应该写出去。比如用户把程序结果重定向到文件或管道时,答案不能只显示在人看的界面里。

数据流:进去的是一段最终消息、标准输出不是终端、标准错误是终端 → 判断逻辑认为需要写标准输出 → 测试用断言确认这个决定为真。

调用关系:它补上了上一条测试的另一种使用场景:输出被机器或文件接走时,最终答案必须可被接收。

调用图:外部调用 1 个(assert!)。

prints_final_stdout_message_when_stderr_is_not_terminal49–55 ↗
fn prints_final_stdout_message_when_stderr_is_not_terminal()

作用:检查只要标准错误不是终端,也应该把最终回答写到标准输出。这样在输出环境不完全是交互屏幕时,程序仍能交出可收集的最终结果。

数据流:进去的是最终消息、标准输出是终端、标准错误不是终端 → 判断逻辑评估当前环境 → 出来的是“需要打印到标准输出”的结果,并由断言确认。

调用关系:它和前两个测试一起覆盖不同输出通道组合,确保最终消息不会在非典型运行环境里丢失。

调用图:外部调用 1 个(assert!)。

suppresses_final_stdout_message_when_missing58–63 ↗
fn suppresses_final_stdout_message_when_missing()

作用:检查根本没有最终消息时,程序不要打印空内容。没有答案就不该假装有答案。

数据流:进去的是空的最终消息和两个非终端标记 → 判断逻辑首先发现没有可打印内容 → 出来的是“不打印”,测试用断言确认。

调用关系:这是最终消息输出规则里的安全兜底测试,防止后续代码在没有内容时产生误导性的空输出。

调用图:外部调用 1 个(assert!)。

prints_final_tty_message_when_not_yet_rendered66–73 ↗
fn prints_final_tty_message_when_not_yet_rendered()

作用:检查在真人终端上,如果最终消息还没显示过,就应该显示它。这样用户能在屏幕上看到完整结论。

数据流:进去的是最终消息、“还没渲染过”的标记,以及两个通道都是终端 → 判断逻辑决定要在终端显示 → 测试确认结果为真。

调用关系:它测试的是面向人看的终端显示规则,和标准输出规则互相配合,避免漏显示。

调用图:外部调用 1 个(assert!)。

suppresses_final_tty_message_when_already_rendered76–83 ↗
fn suppresses_final_tty_message_when_already_rendered()

作用:检查最终消息如果已经在终端显示过,就不要再显示一次。这样可以避免用户看到重复答案。

数据流:进去的是最终消息、“已经渲染过”的标记,以及两个通道都是终端 → 判断逻辑识别出已经显示过 → 出来的是“不再显示”,并由断言确认。

调用关系:它和“尚未显示就要显示”的测试成对出现,共同保护终端输出不会漏也不会重复。

调用图:外部调用 1 个(assert!)。

reasoning_text_prefers_summary_when_raw_reasoning_is_hidden86–94 ↗
fn reasoning_text_prefers_summary_when_raw_reasoning_is_hidden()

作用:检查当设置为不展示原始推理时,程序会显示简短摘要。原始推理可以理解为模型的内部草稿,摘要则是更适合给用户看的版本。

数据流:进去的是一份摘要、一份原始内容,以及“不显示原始推理”的开关 → reasoning_text 选择摘要 → 出来的是摘要文本,测试用 assert_eq! 确认。

调用关系:这个测试直接调用 reasoning_text,确保输出处理器在保护敏感或冗长推理内容时会优先给出摘要。

调用图:外部调用 2 个(assert_eq!, reasoning_text)。

reasoning_text_uses_raw_content_when_enabled97–105 ↗
fn reasoning_text_uses_raw_content_when_enabled()

作用:检查当用户或配置允许显示原始推理时,程序确实使用原始内容。这样调试或高级查看场景能看到更完整的信息。

数据流:进去的是摘要、原始内容,以及“显示原始推理”的开关 → reasoning_text 选择原始内容 → 出来的是原始文本,断言确认完全一致。

调用关系:它和隐藏原始推理的测试互为对照,说明同一个选择函数会按开关切换不同内容。

调用图:外部调用 2 个(assert_eq!, reasoning_text)。

summarizes_disabled_permission_profile_as_danger_full_access108–119 ↗
fn summarizes_disabled_permission_profile_as_danger_full_access()

作用:检查当权限沙箱被关闭时,摘要会明确写成 danger-full-access,也就是危险的完全访问。这样用户一眼知道程序几乎不受限制。

数据流:进去的是一个测试用工作目录和 Disabled 权限配置 → summarize_permission_profile 把它翻译成给人看的短句 → 出来的是 danger-full-access,断言确认。

调用关系:这个测试调用 test_path_buf 造路径,再验证权限摘要函数。它保护的是配置展示里的高风险提示。

调用图:外部调用 2 个(assert_eq!, test_path_buf)。

summarizes_external_permission_profile122–135 ↗
fn summarizes_external_permission_profile()

作用:检查外部沙箱模式会被清楚地说明,并带上网络访问是否开启。外部沙箱指限制不由程序自己管理,而是交给外面的环境。

数据流:进去的是测试工作目录,以及带网络开启状态的 External 权限配置 → 摘要函数生成说明文字 → 出来的是 external-sandbox 加网络开启提示。

调用关系:它继续验证权限摘要函数,确保不同权限模式不会被混成同一种描述。

调用图:外部调用 2 个(assert_eq!, test_path_buf)。

summarizes_managed_workspace_write_permission_profile138–161 ↗
fn summarizes_managed_workspace_write_permission_profile()

作用:检查工作区可写模式的摘要会列出可写的位置。用户需要知道程序能改哪些目录,尤其是除了当前项目外还有缓存目录时。

数据流:进去的是项目目录、缓存目录、受限文件系统策略和受限网络策略 → 测试用 restricted 和 from_runtime_permissions 组装权限配置 → 摘要函数输出 workspace-write,并包含 workdir 和缓存目录。

调用关系:这个测试搭好一个较真实的权限配置,再验证摘要文字。它保护的是用户对“程序能写哪里”的理解。

调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 3 个(assert_eq!, test_path_buf, vec!)。

summarizes_managed_read_only_permission_profile164–175 ↗
fn summarizes_managed_read_only_permission_profile()

作用:检查没有任何可写目录时,权限摘要会显示 read-only,也就是只读。这样用户知道程序不能随便改文件。

数据流:进去的是工作目录、空的受限文件系统规则和受限网络策略 → 测试组装出运行权限配置 → 摘要函数把它变成 read-only,断言确认。

调用关系:它和工作区可写测试形成对比,确保权限摘要能区分“能写”和“只能看”。

调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 3 个(new, assert_eq!, test_path_buf)。

config_summary_entries_include_runtime_workspace_roots178–241 ↗
async fn config_summary_entries_include_runtime_workspace_roots()

作用:检查配置摘要里会包含运行时加入的工作区根目录。否则用户可能不知道程序除了当前目录,还被允许访问哪个额外目录。

数据流:进去的是临时创建的 codex 主目录、当前目录、额外工作区目录,以及构造出来的配置和会话事件 → 测试把工作区根目录写入配置和权限里,再调用 config_summary_entries → 出来的是配置摘要列表,其中 sandbox 那一项必须包含额外目录名。

调用关系:这是一个异步测试,因为构建配置需要等待。它调用 ConfigBuilder、workspace_write_with 和 config_summary_entries,模拟启动后整理配置摘要的流程。

调用图:调用 3 个内部函数(workspace_write_with, new, new);外部调用 5 个(assert!, default, config_summary_entries, tempdir, vec!)。

final_message_from_turn_items_uses_latest_agent_message244–265 ↗
fn final_message_from_turn_items_uses_latest_agent_message()

作用:检查从一轮对话的项目列表里提取最终消息时,会选择最后一条智能体消息。因为更晚的回答通常才是最终答案。

数据流:进去的是包含第一条回答、计划、第二条回答的列表 → final_message_from_turn_items 从后往前找合适内容 → 出来的是 second,断言确认。

调用关系:它直接验证最终答案提取函数,为后面的 TurnCompleted 事件处理测试打基础。

调用图:外部调用 2 个(assert_eq!, final_message_from_turn_items)。

final_message_from_turn_items_falls_back_to_latest_plan268–286 ↗
fn final_message_from_turn_items_falls_back_to_latest_plan()

作用:检查如果列表里没有智能体回答,程序会退而求其次使用最后一条计划。这样至少还能给用户留下一个有意义的结尾。

数据流:进去的是推理内容和两条计划,没有正式智能体消息 → final_message_from_turn_items 找不到回答后选择最后的计划 → 出来的是 final plan。

调用关系:它补充测试最终答案提取函数的备用规则,避免某些会话结束时完全没有可展示内容。

调用图:外部调用 4 个(new, assert_eq!, final_message_from_turn_items, vec!)。

turn_completed_recovers_final_message_from_turn_items289–334 ↗
fn turn_completed_recovers_final_message_from_turn_items()

作用:检查一轮对话完成时,如果事件里带着完整项目列表,输出处理器能从里面恢复最终答案。这样即使答案不是靠流式输出保存的,也不会丢。

数据流:进去的是一个新建的 EventProcessorWithHumanOutput,以及一条 TurnCompleted 服务端通知,通知里有完成状态和一条最终回答 → process_server_notification 处理通知并更新处理器内部状态 → 出来的是要求关闭的状态,并且 final_message 变成 final answer。

调用关系:这个测试把假的服务端完成事件交给输出处理器,验证它会调用自己的事件处理流程并保存最终消息。

调用图:外部调用 4 个(TurnCompleted, new, assert_eq!, vec!)。

turn_completed_overwrites_stale_final_message_from_turn_items337–383 ↗
fn turn_completed_overwrites_stale_final_message_from_turn_items()

作用:检查一轮对话完成后,如果处理器里已有旧答案,新事件里的最终答案会覆盖它。这样用户不会看到上一轮残留的过期内容。

数据流:进去的是带有 stale answer 的处理器,以及含 final answer 的完成通知 → 处理器处理通知,发现项目列表里有新最终消息 → 出来的是 final_message 被改成 final answer,并且 final_message_rendered 被重置为未显示。

调用关系:它继续测试 TurnCompleted 处理路径,重点看状态更新是否能清掉旧痕迹。

调用图:外部调用 5 个(TurnCompleted, new, assert!, assert_eq!, vec!)。

turn_completed_preserves_streamed_final_message_when_turn_items_are_empty386–427 ↗
fn turn_completed_preserves_streamed_final_message_when_turn_items_are_empty()

作用:检查一轮完成时如果项目列表为空,之前通过流式过程收到的答案会被保留。流式过程就是答案边生成边到达,像边打字边显示。

数据流:进去的是已经保存 streamed answer 的处理器,以及一个完成但 items 为空的通知 → 处理器没有从项目列表找到新答案,于是保留旧的流式答案 → 出来的是 final_message 仍为 streamed answer,并标记关机时还要输出最终消息。

调用关系:这个测试覆盖了完成事件信息不完整的情况,确保处理器不会因为空列表误删已经收到的答案。

调用图:外部调用 5 个(TurnCompleted, new, new, assert!, assert_eq!)。

turn_failed_clears_stale_final_message430–472 ↗
fn turn_failed_clears_stale_final_message()

作用:检查一轮对话失败时,旧的或半截的最终消息会被清空。失败的回答不能被当成正式答案交给用户。

数据流:进去的是保存着 partial answer 且准备关机输出的处理器,以及状态为 Failed 的完成通知 → 处理器识别失败状态并清理最终消息相关标记 → 出来的是 final_message 为空,已渲染标记和关机输出标记都变为 false。

调用关系:它测试 TurnCompleted 通知里的失败分支,保护错误场景下不会输出误导性的残留答案。

调用图:外部调用 5 个(TurnCompleted, new, new, assert!, assert_eq!)。

turn_interrupted_clears_stale_final_message475–517 ↗
fn turn_interrupted_clears_stale_final_message()

作用:检查一轮对话被中断时,也会清空旧的或半成品最终消息。中断说明流程没正常结束,不能把残留内容当最终结论。

数据流:进去的是带 partial answer 的处理器,以及状态为 Interrupted 的完成通知 → 处理器按中断状态清理最终消息、渲染标记和关机输出标记 → 出来的是这些状态都被复位。

调用关系:它和失败场景测试类似,但覆盖的是被用户或系统打断的分支,确保所有非正常结束都会清理输出状态。

调用图:外部调用 5 个(TurnCompleted, new, new, assert!, assert_eq!)。

exec/src/event_processor_with_jsonl_output_tests.rs源码 ↗
testtest

这个文件不参与正式运行,而是在测试时模拟服务器发来的通知,检查事件处理器的反应是否正确。可以把事件处理器想成一个“翻译员”:服务器说“消息完成了”“任务失败了”“有警告”“工具调用完成了”,它要把这些话翻译成命令行执行模式需要的事件和最终输出。这里测试了三件重要的事:第一,如果一次对话回合失败了,就算之前收到过半截回答,也不能把它写进“最后消息”文件,免得旧文件被错误覆盖。第二,运行时警告要变成一条普通错误项目继续输出,而不是直接让程序崩掉。第三,MCP 工具调用结果里的 _meta 信息要完整保留下来,并且序列化成 JSON 时字段名仍然是 _meta,不是普通的 meta。这些测试像安全网,防止以后改代码时不小心破坏对外输出格式。

函数细节3
failed_turn_does_not_overwrite_output_last_message_file7–60 ↗
fn failed_turn_does_not_overwrite_output_last_message_file()

作用:这个测试确认:如果一次对话回合最后失败了,程序不能把之前收到的半截回答写进“最终消息”文件。这样可以避免失败结果污染用户原本已有的输出文件。

数据流:测试先创建一个临时目录和一个已有内容的 last-message.txt 文件,里面写着“keep existing contents”。然后它创建 EventProcessorWithJsonOutput,把一条“助手消息已完成”的通知喂进去,确认处理器暂时记住了“partial answer”。接着它再喂一条“回合失败”的通知,处理器把状态变成准备关闭,并清空最终消息。最后调用 print_final_output,再读回文件,确认文件内容仍然是原来的文字,没有被半截失败答案覆盖。

调用关系:这个测试直接使用 EventProcessorWithJsonOutput::new 建出被测对象,用 ItemCompleted 和 TurnCompleted 模拟服务器先给出回答、后报告失败的过程。最后它调用 EventProcessor::print_final_output,专门检查真正写最终输出的那一步有没有犯错。assert_eq!、tempdir 和 write 只是帮它搭测试环境和核对结果。

调用图:调用 2 个内部函数(print_final_output, new);外部调用 6 个(ItemCompleted, TurnCompleted, new, assert_eq!, write, tempdir)。

runtime_warning_emits_a_non_fatal_error_item63–87 ↗
fn runtime_warning_emits_a_non_fatal_error_item()

作用:这个测试确认:运行中收到警告时,处理器会把它变成一条可输出的错误项目,但不会把整个流程判定为失败或立刻关闭。也就是说,警告会被看见,但不至于让程序停摆。

数据流:测试先创建一个不写最后消息文件的处理器。然后它喂入一条 Warning 通知,内容是“invalid global instructions”。处理器把这条警告转换成一个 ThreadEvent::ItemCompleted 事件,里面的项目类型是 Error,消息保持原样,同时整体状态仍然是 CodexStatus::Running。测试最后用 assert_eq! 对比完整结果,确认事件内容和运行状态都符合预期。

调用关系:这个测试覆盖的是服务器发来 Warning 时的分支。它通过 EventProcessorWithJsonOutput::new 建处理器,再用 Warning 构造外部通知,随后只检查 collect_thread_events 的输出,不进入最终打印阶段。它保护的是“警告非致命”这条约定。

调用图:调用 1 个内部函数(new);外部调用 2 个(Warning, assert_eq!)。

mcp_tool_call_result_preserves_meta_in_jsonl_event90–138 ↗
fn mcp_tool_call_result_preserves_meta_in_jsonl_event()

作用:这个测试确认:MCP 工具调用结果里的元数据不会在事件转换或 JSON 序列化时丢失。特别是输出 JSON 里必须叫 _meta,因为这通常是外部协议要求的字段名。

数据流:测试先创建处理器,然后构造一条 MCP 工具调用完成的通知。通知里带有工具名、参数、结果内容,以及 meta 元数据,例如 raw_messages 和 ref_id。处理器收到后产出一个事件,测试先确认状态还在运行、事件只有一条,再从事件里取出 MCP 工具调用项目,检查 result.meta 还保留着原始内容。最后它把事件转成 JSON 值,确认 JSON 结果中有 result._meta,并且没有错误地出现 result.meta

调用关系:这个测试模拟服务器通过 ItemCompleted 报告一次 MCP 工具调用完成。它依赖 json! 构造测试用 JSON 数据,调用 collect_thread_events 触发转换,再用 serde_json::to_value 检查最终对外 JSON 形状。panic! 只在事件类型不符合预期时让测试立刻失败,assert_eq! 和 assert! 用来守住字段和值。

调用图:调用 1 个内部函数(new);外部调用 8 个(new, ItemCompleted, assert!, assert_eq!, json!, panic!, to_value, vec!)。

exec/tests/event_processor_with_json_output.rs源码 ↗
testtest suite

这个测试文件把各种服务器发来的通知当作“原材料”,喂给 EventProcessorWithJsonOutput,然后检查它吐出的线程事件是不是符合预期。可以把它理解成翻译器的验收清单:服务器说“命令开始了”,输出端就该看到“某个命令项开始了”;服务器说“回合结束了”,输出端就该看到完成事件,并带上用量统计和最终回答。测试覆盖了命令执行、网页搜索、MCP 工具调用(外部工具调用)、协作智能体、文件修改、待办计划、错误和模型改路由等场景。它还特别检查一些容易出错的细节,比如忽略空推理内容、不要泄露原始推理、同一个项目开始和完成要复用同一个编号、失败时清掉过期最终回答。

函数细节30
map_todo_items_preserves_text_and_completion_state79–104 ↗
fn map_todo_items_preserves_text_and_completion_state()

作用:这个测试确认计划步骤转换成待办事项时,文字不会变,完成状态也不会弄反。有人改待办列表转换规则时,它能立刻发现“进行中”和“已完成”被翻错的问题。

数据流:进去的是两个计划步骤:一个进行中,一个已完成 → 测试调用 map_todo_items 把它们变成 TodoItem → 出来应该是同样两段文字,并且只有已完成的那一项 completed 为 true;测试用 assert_eq! 比较结果。

调用关系:Rust 测试运行器会单独运行它。它直接检查 EventProcessorWithJsonOutput::map_todo_items 这个小转换函数,不经过完整通知流程,是后面计划更新测试的基础校验。

调用图:调用 1 个内部函数(map_todo_items);外部调用 1 个(assert_eq!)。

session_configured_produces_thread_started_event107–137 ↗
fn session_configured_produces_thread_started_event()

作用:这个测试确认会话配置完成后,会被翻译成“线程已开始”的事件。没有这个行为,外部输出可能不知道一段新对话已经正式开始。

数据流:进去的是一个 SessionConfiguredEvent,里面有线程 ID、模型、权限、工作目录等配置 → 测试调用 thread_started_event 做转换 → 出来应该是 ThreadStarted 事件,并且线程 ID 字符串保持一致。

调用关系:测试运行时先用 from_string、from、read_only 和 test_path_buf 准备一份真实形状的配置,再把转换结果交给 assert_eq! 核对。它验证的是启动阶段从协议事件到执行层事件的第一步。

调用图:调用 3 个内部函数(read_only, from, from_string);外部调用 2 个(assert_eq!, test_path_buf)。

turn_started_emits_turn_started_event140–165 ↗
fn turn_started_emits_turn_started_event()

作用:这个测试确认服务器通知“一个回合开始了”时,处理器会输出“回合开始”事件,并保持运行状态。这样下游才能知道助手已经开始处理本轮请求。

数据流:进去的是 TurnStarted 通知,包含线程和回合信息 → 新建的处理器 collect_thread_events 接收它 → 出来是一个 TurnStarted 事件,状态是 Running,没有触发关闭。

调用关系:测试运行器调用它后,它先用 new 创建 EventProcessorWithJsonOutput,再构造 TurnStarted 通知,最后用 assert_eq! 检查处理器输出。它覆盖主事件收集流程的开头。

调用图:调用 1 个内部函数(new);外部调用 3 个(TurnStarted, new, assert_eq!)。

command_execution_started_and_completed_translate_to_thread_events168–244 ↗
fn command_execution_started_and_completed_translate_to_thread_events()

作用:这个测试确认命令执行从开始到完成都会被正确翻译。它保证用户看到的命令、输出、退出码和状态都对得上。

数据流:进去先是一个正在执行的 ls 命令通知 → 处理器生成 ItemStarted,并给它一个内部编号 item_0 → 后来进去同一个命令的完成通知,带输出 a.txt 和退出码 0 → 出来是 ItemCompleted,复用 item_0,并带上完整输出和完成状态。

调用关系:它用 new 建处理器,用 ItemStarted 和 ItemCompleted 模拟服务器的两段通知,用 test_path_buf 准备工作目录。它验证 collect_thread_events 在命令类项目上的编号复用和字段映射。

调用图:调用 1 个内部函数(new);外部调用 5 个(ItemCompleted, ItemStarted, new, assert_eq!, test_path_buf)。

empty_reasoning_items_are_ignored247–270 ↗
fn empty_reasoning_items_are_ignored()

作用:这个测试确认没有摘要的推理项目会被忽略。这样可以避免把不该展示或没有价值的内部思考内容发给外部输出。

数据流:进去的是一个 Reasoning 项,summary 为空,但 content 里有原始推理文字 → 处理器接收完成通知 → 出来没有任何事件,状态仍然是 Running。

调用关系:它通过 ItemCompleted 通知走完整事件收集入口。assert_eq! 检查空事件列表,说明处理器没有把原始 content 当成可展示内容。

调用图:调用 1 个内部函数(new);外部调用 4 个(ItemCompleted, new, assert_eq!, vec!)。

unsupported_items_do_not_consume_synthetic_ids273–324 ↗
fn unsupported_items_do_not_consume_synthetic_ids()

作用:这个测试确认不支持展示的项目被忽略时,不会浪费内部生成的编号。否则后面的可见项目编号会跳号,影响输出稳定性。

数据流:进去先是一个 Plan 项完成通知,但这种项目在这里不直接输出 → 处理器返回空事件,并没有消耗 item_0 → 接着进去一个 AgentMessage → 出来它拿到的编号仍然是 item_0。

调用关系:它连续调用 collect_thread_events 两次,先验证忽略行为,再验证下一条真正输出的消息编号。它保护的是处理器内部“合成 ID”计数器的正确性。

调用图:调用 1 个内部函数(new);外部调用 2 个(ItemCompleted, assert_eq!)。

reasoning_items_emit_summary_not_raw_content327–357 ↗
fn reasoning_items_emit_summary_not_raw_content()

作用:这个测试确认推理项目只输出安全摘要,不输出原始推理内容。它很重要,因为原始推理可能不适合直接展示给用户。

数据流:进去的是一个 Reasoning 完成通知,summary 是 safe summary,content 是 raw reasoning → 处理器转换它 → 出来的是 ReasoningItem,文本只包含 safe summary。

调用关系:它通过 ItemCompleted 走普通项目完成流程,并用 assert_eq! 明确检查输出文本。它和空推理测试一起约束推理内容的展示边界。

调用图:调用 1 个内部函数(new);外部调用 3 个(ItemCompleted, assert_eq!, vec!)。

web_search_completion_preserves_query_and_action360–398 ↗
fn web_search_completion_preserves_query_and_action()

作用:这个测试确认网页搜索完成后,搜索词和搜索动作不会丢。这样外部输出能准确告诉用户系统搜了什么。

数据流:进去的是 WebSearch 完成通知,带 query 和 Search action → 处理器把协议层的搜索动作转换成执行层的 WebSearchAction → 出来是 ItemCompleted,里面保留原始搜索词和动作细节。

调用关系:它直接模拟一次搜索完成,不测试开始阶段。collect_thread_events 负责翻译通知,assert_eq! 检查搜索项目的字段是否逐项保留。

调用图:调用 1 个内部函数(new);外部调用 2 个(ItemCompleted, assert_eq!)。

web_search_start_and_completion_reuse_item_id401–467 ↗
fn web_search_start_and_completion_reuse_item_id()

作用:这个测试确认同一次网页搜索开始和完成时使用同一个内部编号。这样前端或日志能知道“开始搜索”和“搜索结束”说的是同一件事。

数据流:进去先是 WebSearch 开始通知,搜索词还为空、动作未知 → 出来 ItemStarted,编号 item_0,动作是 Other → 再进去同一搜索 ID 的完成通知,带真正搜索词和动作 → 出来 ItemCompleted,仍然是 item_0。

调用关系:它用 ItemStarted 和 ItemCompleted 模拟搜索生命周期。处理器内部需要记住服务器给的 search-1 和自己生成的 item_0 的对应关系。

调用图:调用 1 个内部函数(new);外部调用 4 个(ItemCompleted, ItemStarted, new, assert_eq!)。

mcp_tool_call_begin_and_end_emit_item_events470–557 ↗
fn mcp_tool_call_begin_and_end_emit_item_events()

作用:这个测试确认 MCP 工具调用开始和结束都会变成可输出的项目事件。MCP 可以理解成“让模型调用外部工具的一套接口”,这里要保证工具名、参数、结果和状态都正确。

数据流:进去先是一个进行中的 MCP 工具调用,带服务器名、工具名和 JSON 参数 → 出来 ItemStarted,状态 InProgress → 再进去完成通知,带空结果内容和耗时 → 出来 ItemCompleted,复用编号并标记 Completed。

调用关系:它通过 json! 构造工具参数,用 ItemStarted 和 ItemCompleted 走完整生命周期。这个测试保护 MCP 调用在 JSON 输出中的基本可见性。

调用图:调用 1 个内部函数(new);外部调用 6 个(new, ItemCompleted, ItemStarted, new, assert_eq!, json!)。

mcp_tool_call_failure_sets_failed_status560–606 ↗
fn mcp_tool_call_failure_sets_failed_status()

作用:这个测试确认 MCP 工具失败时,输出里会明确标成失败,并保留错误消息。否则用户只会看到工具没结果,却不知道是失败了。

数据流:进去的是一个 MCP 工具完成通知,但状态是 Failed,并带 error.message 为 tool exploded → 处理器转换状态和错误对象 → 出来是 ItemCompleted,状态 Failed,错误消息保留。

调用关系:它只测试失败完成这一段,通过 collect_thread_events 进入项目完成处理。assert_eq! 检查失败状态和错误结构,补齐成功调用测试没有覆盖的分支。

调用图:调用 1 个内部函数(new);外部调用 3 个(ItemCompleted, assert_eq!, json!)。

mcp_tool_call_defaults_arguments_and_preserves_structured_content609–702 ↗
fn mcp_tool_call_defaults_arguments_and_preserves_structured_content()

作用:这个测试确认 MCP 工具参数即使是空值也会原样保留,同时结构化结果不会丢。结构化结果就是机器更容易读懂的 JSON 结果,不只是普通文字。

数据流:进去先是 arguments 为 Null 的工具开始通知 → 出来仍保留 Null 参数 → 后来完成通知带文本内容和 structured_content {status: ok} → 出来结果同时保留 content 和 structured_content。

调用关系:它用 json! 和 vec! 构造结果内容,经过 ItemStarted、ItemCompleted 两次调用处理器。它保护工具结果中“给人看”和“给程序读”的两类信息都能输出。

调用图:调用 1 个内部函数(new);外部调用 6 个(new, ItemCompleted, ItemStarted, assert_eq!, json!, vec!)。

collab_spawn_begin_and_end_emit_item_events705–794 ↗
fn collab_spawn_begin_and_end_emit_item_events()

作用:这个测试确认启动协作智能体的工具调用会正确输出开始和完成事件。协作智能体就是让另一个子线程或子助手一起干活,这里要让外部看见它被派出去了。

数据流:进去先是 SpawnAgent 调用开始通知,带父线程、提示词和模型 → 出来 ItemStarted,状态 InProgress → 再进去完成通知,带子线程 ID 和子智能体运行状态 → 出来 ItemCompleted,复用编号并保留子线程和状态表。

调用关系:它模拟协作工具的完整生命周期。处理器把协议层的 CollabAgentTool、状态和 agents_states 转成 codex_exec 自己的类型,assert_eq! 验证这些信息没有丢。

调用图:调用 1 个内部函数(new);外部调用 7 个(ItemCompleted, ItemStarted, new, assert_eq!, from, new, vec!)。

file_change_completion_maps_change_kinds797–857 ↗
fn file_change_completion_maps_change_kinds()

作用:这个测试确认文件修改完成后,新增、删除、更新三种变化会被正确翻译。这样用户能清楚看到项目里哪些文件被加了、删了、改了。

数据流:进去的是 FileChange 完成通知,包含三个文件变化:Add、Delete、Update → 处理器丢掉 diff 细节,只保留路径和变化类型 → 出来是 FileChangeItem,状态 Completed,三种变化类型对应正确。

调用关系:它通过 ItemCompleted 测试补丁应用结果的输出。assert_eq! 检查协议层 PatchChangeKind 到执行层 PatchChangeKind 的映射。

调用图:调用 1 个内部函数(new);外部调用 3 个(ItemCompleted, assert_eq!, vec!)。

file_change_declined_maps_to_failed_status860–898 ↗
fn file_change_declined_maps_to_failed_status()

作用:这个测试确认文件修改被拒绝时,在输出层会当作失败处理。这样用户不会误以为补丁已经成功应用。

数据流:进去的是 FileChange 完成通知,但 status 是 Declined → 处理器把它转换成 PatchApplyStatus::Failed → 出来是一个 FileChangeItem,包含文件路径和更新类型,整体状态为 Failed。

调用关系:它覆盖文件修改状态映射里的拒绝分支。collect_thread_events 做转换,assert_eq! 确认“拒绝”不会被误报成“完成”。

调用图:调用 1 个内部函数(new);外部调用 3 个(ItemCompleted, assert_eq!, vec!)。

agent_message_item_updates_final_message901–933 ↗
fn agent_message_item_updates_final_message()

作用:这个测试确认助手消息完成后,会同时输出消息事件,并记录为最终回答。最终回答常用于命令行结束时打印最后一句结果。

数据流:进去的是 AgentMessage 完成通知,文本是 hello → 处理器输出 ItemCompleted,内容是 AgentMessageItem → 同时处理器内部 final_message 变成 hello。

调用关系:它先通过 collect_thread_events 走项目完成流程,再调用 final_message 读取处理器内部保存的最终文本。它验证事件输出和内部状态更新是同步的。

调用图:调用 1 个内部函数(new);外部调用 2 个(ItemCompleted, assert_eq!)。

agent_message_item_started_is_ignored936–959 ↗
fn agent_message_item_started_is_ignored()

作用:这个测试确认助手消息的“开始”通知不会立刻输出。因为消息刚开始时内容还不稳定,真正要展示的是完成后的文本。

数据流:进去的是 AgentMessage 开始通知,虽然带了 hello 文本 → 处理器忽略它 → 出来空事件列表,状态还是 Running。

调用关系:它通过 ItemStarted 走开始事件入口。这个测试和 agent_message_item_updates_final_message 配合,说明助手消息只在完成时产生可见输出。

调用图:调用 1 个内部函数(new);外部调用 2 个(ItemStarted, assert_eq!)。

reasoning_item_completed_uses_synthetic_id962–992 ↗
fn reasoning_item_completed_uses_synthetic_id()

作用:这个测试确认推理摘要完成后会拿到处理器生成的内部编号。这个编号让输出里的项目顺序稳定,不依赖服务器原始 ID。

数据流:进去的是 Reasoning 完成通知,服务器 ID 是 rs-1,摘要是 thinking... → 处理器生成 item_0 → 出来 ReasoningItem 使用 item_0,文本是摘要。

调用关系:它测试项目完成流程中的合成 ID 行为。assert_eq! 明确要求输出 ID 不是 rs-1,而是处理器自己的 item_0。

调用图:调用 1 个内部函数(new);外部调用 3 个(ItemCompleted, assert_eq!, vec!)。

warning_event_produces_error_item995–1016 ↗
fn warning_event_produces_error_item()

作用:这个测试确认警告消息会被包装成一个错误样式的输出项目。这样用户能在统一的事件流里看到重要提醒。

数据流:进去的是一段长警告文字 → 测试调用 collect_warning → 出来是 ItemCompleted,里面是 ErrorItem,message 完整保留,状态仍为 Running。

调用关系:它没有通过 ServerNotification,而是直接调用处理器的警告收集方法。这个路径用于把非普通线程通知的提醒也送进同一套输出格式。

调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。

plan_update_emits_started_then_updated_then_completed1019–1147 ↗
fn plan_update_emits_started_then_updated_then_completed()

作用:这个测试确认计划更新会先创建待办列表,再更新它,回合结束时再完成它。这样用户能看到任务清单从开始、变化到收尾的过程。

数据流:进去第一次 TurnPlanUpdated,两个步骤都未完成 → 出来 ItemStarted,创建 item_0 待办列表 → 第二次进去计划更新,第一步完成 → 出来 ItemUpdated,仍是 item_0 → 最后进去 TurnCompleted → 出来先完成待办列表,再输出 TurnCompleted,并要求关闭。

调用关系:它连续发送 TurnPlanUpdated、TurnPlanUpdated、TurnCompleted。处理器需要记住当前待办列表 ID 和最新内容,并在回合完成时把未完成的列表收尾。

调用图:调用 1 个内部函数(new);外部调用 5 个(TurnCompleted, TurnPlanUpdated, new, assert_eq!, vec!)。

plan_update_after_completion_starts_new_todo_list_with_new_id1150–1209 ↗
fn plan_update_after_completion_starts_new_todo_list_with_new_id()

作用:这个测试确认一个待办列表已经随回合完成后,后续新的计划更新会创建新列表和新编号。否则新回合的计划可能错误地接在旧列表上。

数据流:进去先是一轮计划更新,创建 item_0 → 再进去回合完成,把 item_0 收尾 → 后来新的 TurnPlanUpdated 到来 → 处理器创建新的待办列表,编号是 item_1。

调用关系:它模拟两个回合之间的状态切换。处理器在 TurnCompleted 后必须清理当前待办列表状态,下一次 TurnPlanUpdated 才会走新建分支。

调用图:调用 1 个内部函数(new);外部调用 5 个(TurnCompleted, TurnPlanUpdated, new, assert_eq!, vec!)。

token_usage_update_is_emitted_on_turn_completion1212–1276 ↗
fn token_usage_update_is_emitted_on_turn_completion()

作用:这个测试确认 token 用量更新不会立刻输出,而是在回合完成事件里一起带出。token 可以理解成模型读写文字的计费/计量单位。

数据流:进去先是 ThreadTokenUsageUpdated,包含输入、缓存输入、输出、推理输出 token 数 → 处理器只记录,不产生事件 → 再进去 TurnCompleted → 出来 TurnCompletedEvent,usage 字段带上刚才记录的数字,并要求关闭。

调用关系:它先测试用量通知的缓存行为,再测试回合完成时的汇总输出。collect_thread_events 需要把两个不同通知串起来理解。

调用图:调用 1 个内部函数(new);外部调用 4 个(ThreadTokenUsageUpdated, TurnCompleted, new, assert_eq!)。

turn_completion_recovers_final_message_from_turn_items1279–1313 ↗
fn turn_completion_recovers_final_message_from_turn_items()

作用:这个测试确认即使之前没有单独收到助手消息完成事件,回合完成通知里的 items 也能补回最终回答。这样在只收到汇总数据时也不会丢答案。

数据流:进去的是 TurnCompleted,turn.items 里包含一个 AgentMessage,文本 final answer → 处理器输出 TurnCompletedEvent → 同时 final_message 被设置为 final answer。

调用关系:它只发送 TurnCompleted 通知,不发送 ItemCompleted。处理器在回合收尾时会扫描 turn.items,从里面恢复最终消息。

调用图:调用 1 个内部函数(new);外部调用 3 个(TurnCompleted, assert_eq!, vec!)。

turn_completion_reconciles_started_items_from_turn_items1316–1404 ↗
fn turn_completion_reconciles_started_items_from_turn_items()

作用:这个测试确认某个项目只收到“开始”通知、没收到单独“完成”通知时,回合完成里的完整 items 能把它补成完成事件。这样不会留下永远进行中的命令。

数据流:进去先是命令 ls 的 ItemStarted → 出来 item_0 进行中 → 再进去 TurnCompleted,turn.items 里同一命令已经 Completed,带输出和退出码 → 出来先补一个 ItemCompleted,再输出 TurnCompleted。

调用关系:它验证处理器对流式通知和最终汇总通知的“对账”能力。处理器要把服务器原始 cmd-1 对应到之前的 item_0,然后用最终 items 补齐结果。

调用图:调用 1 个内部函数(new);外部调用 6 个(ItemStarted, TurnCompleted, new, assert_eq!, test_path_buf, vec!)。

turn_completion_overwrites_stale_final_message_from_turn_items1407–1454 ↗
fn turn_completion_overwrites_stale_final_message_from_turn_items()

作用:这个测试确认回合完成时,如果汇总 items 给出了最终回答,它会覆盖之前流式收到的旧答案。这样最终保存的是权威结果。

数据流:进去先是 AgentMessage 完成通知,记录 stale answer → 后来 TurnCompleted 的 items 里有 AgentMessage final answer → 处理器输出完成事件,并把 final_message 改成 final answer。

调用关系:它先走 ItemCompleted 更新内部最终消息,再走 TurnCompleted 的最终汇总修正路径。这个测试防止处理器保留过期答案。

调用图:调用 1 个内部函数(new);外部调用 4 个(ItemCompleted, TurnCompleted, assert_eq!, vec!)。

turn_completion_preserves_streamed_final_message_when_turn_items_are_empty1457–1499 ↗
fn turn_completion_preserves_streamed_final_message_when_turn_items_are_empty()

作用:这个测试确认如果回合完成通知里没有 items,处理器会保留之前流式收到的最终回答。这样汇总为空时不会把已有答案清掉。

数据流:进去先是 AgentMessage 完成通知,记录 streamed answer → 再进去 TurnCompleted,但 turn.items 是空 → 出来 TurnCompletedEvent,final_message 仍然是 streamed answer。

调用关系:它覆盖和“覆盖旧答案”相反的情况。处理器只有在完成通知提供了更权威的消息时才覆盖,否则保留已有流式结果。

调用图:调用 1 个内部函数(new);外部调用 4 个(ItemCompleted, TurnCompleted, new, assert_eq!)。

failed_turn_clears_stale_final_message1502–1544 ↗
fn failed_turn_clears_stale_final_message()

作用:这个测试确认回合失败时,会清掉之前可能收到的部分答案。这样失败请求不会留下看似成功的最终回答。

数据流:进去先是 AgentMessage 完成通知,final_message 变成 partial answer → 再进去状态为 Failed 的 TurnCompleted,带错误 turn failed → 处理器状态变为 InitiateShutdown,并把 final_message 清成 None。

调用关系:它把普通消息完成和失败回合收尾连在一起测试。处理器在失败分支里必须主动清理之前的成功样式状态。

调用图:调用 1 个内部函数(new);外部调用 4 个(ItemCompleted, TurnCompleted, new, assert_eq!)。

turn_completion_falls_back_to_final_plan_text1547–1579 ↗
fn turn_completion_falls_back_to_final_plan_text()

作用:这个测试确认如果回合没有助手消息,但有最终 Plan 文本,处理器会把计划文本当作最终消息。这样某些只返回计划的场景也有可保存的结论。

数据流:进去的是 TurnCompleted,turn.items 里只有一个 Plan,文本 ship the typed adapter → 处理器输出 TurnCompletedEvent → final_message 被设置成这段计划文本。

调用关系:它测试回合完成扫描 items 时的备用规则:优先找助手消息,找不到时可用计划文本。assert_eq! 检查最终消息被正确回填。

调用图:调用 1 个内部函数(new);外部调用 3 个(TurnCompleted, assert_eq!, vec!)。

turn_failure_prefers_structured_error_message1582–1631 ↗
fn turn_failure_prefers_structured_error_message()

作用:这个测试确认失败回合会优先使用之前收到的结构化错误消息。结构化错误就是拆成主消息和附加详情的错误,比普通失败状态更清楚。

数据流:进去先是 Error 通知,主消息 backend failed,附加详情 request id abc → 处理器输出 Error 事件,并记住合成后的消息 → 再进去 Failed 的 TurnCompleted,且自身没有 error → 出来 TurnFailed,错误消息仍是 backend failed (request id abc)。

调用关系:它先通过 Error 通知建立最近错误,再通过 TurnCompleted 的失败分支消费这个错误。处理器需要在两个通知之间保存错误上下文。

调用图:调用 1 个内部函数(new);外部调用 4 个(Error, TurnCompleted, new, assert_eq!)。

model_reroute_surfaces_as_error_item1634–1659 ↗
fn model_reroute_surfaces_as_error_item()

作用:这个测试确认模型被自动改路由时,会作为一个可见的错误/提醒项目输出。这样用户知道原本的模型被换成了另一个模型,以及原因是什么。

数据流:进去的是 ModelRerouted 通知,from_model 是 gpt-5,to_model 是 gpt-5-mini,原因是 HighRiskCyberActivity → 处理器生成一个 ItemCompleted → 出来 item_0 的 details 是 ErrorItem,消息写明改路由前后模型和原因。

调用关系:它通过 collect_thread_events 处理模型改路由通知,然后手动匹配输出事件类型;如果不是 ItemCompleted 就 panic。它覆盖一种不是普通失败、但必须让用户看见的系统提示。

调用图:调用 1 个内部函数(new);外部调用 3 个(ModelRerouted, assert_eq!, panic!)。

集成测试框架

这些文件定义共享集成测试 crate 和模块索引,将 exec 端到端套件汇集到一个可运行的测试二进制文件中。

exec/tests/all.rs源码 ↗
testtest run

这个文件本身不写具体测试内容,而是把测试模块接进来。Rust 的集成测试通常放在 tests 目录下,每个测试文件会被当成一个单独的测试程序;这里选择用一个统一的 all.rs 来汇总测试,避免测试入口太分散。mod suite; 会加载 tests/suite/ 里的测试集合,mod event_processor_with_json_output; 会加载另一个专门测试 JSON 输出事件处理的模块。最上面的 #![allow(clippy::expect_used)] 是告诉代码检查工具 Clippy:这个测试程序里允许使用 expect,因为测试代码经常需要在失败时直接报错并说明原因。没有这个文件,相关测试模块可能不会被这个统一的集成测试二进制包含,测试运行方式也会更零散。

exec/tests/suite/mod.rs源码 ↗
testtest discovery and test run startup

这个文件本身不写具体测试,也没有函数。它的作用是告诉 Rust 测试系统:这里有一组测试模块,比如补丁应用、沙箱、恢复会话、标准输入提示、服务器错误退出等,都要一起纳入测试。可以把它理解成一本练习册的目录页:目录页不做题,但它决定哪些章节会被翻到、会被检查。每一行 mod xxx; 都是在声明一个同目录下的测试文件或测试模块,让编译器把它编进测试程序里。这样做的好处是测试入口统一,后续新增或整理测试时,只要在这里挂上模块名,就能让整套测试一起跑起来。

请求构建与提示输入

这些集成测试覆盖 codex-exec 如何根据提示、stdin、工作区指令、认证、headers、schema 和可写目录标志构建出站请求。

exec/tests/suite/add_dir.rs源码 ↗
testtest execution

这个测试文件像是在检查一个入口的“钥匙孔”有没有装对:用户运行 Codex 执行命令时,可以用 --add-dir 额外指定一些目录,让沙箱也能访问这些地方。沙箱可以理解成一个受限制的工作区,防止程序乱碰不该碰的文件。这里不真正连接线上服务,而是启动一个假的服务器,让它按预设返回“任务完成”的消息。测试再创建几个临时目录,把它们通过 --add-dir 传给命令行,然后确认命令能成功结束,退出码是 0。重点不是验证目录里的文件怎么被读写,而是确认这个命令行参数能被接受、能重复出现,并且能一路传到执行流程里,不会在启动阶段报错。

函数细节2
accepts_add_dir_flag10–38 ↗
async fn accepts_add_dir_flag() -> anyhow::Result<()>

作用:这个测试确认 --add-dir 参数最基本的用法是通的:传两个额外目录时,命令应该能正常跑完。有人改命令行参数解析或沙箱设置时,这个测试能及时发现有没有把这个参数弄坏。

数据流:测试开始后,先创建一个测试用的命令执行器,再启动一个假的服务器,并准备好一段模拟返回内容:创建响应、助手说“Task completed”、响应完成。接着它创建两个临时目录,把这两个目录作为两次 --add-dir 参数传进命令,同时指定跳过 Git 仓库检查和使用 workspace-write 沙箱。最后它运行命令并检查结果:程序退出码必须是 0,表示命令被接受并成功结束。

调用关系:它是一个独立的异步测试,由测试框架在测试运行时调用。它会借助 test_codex_exec 准备命令,借助 start_mock_server 启动假服务,用 sse 组装模拟的服务器事件流,再用 mount_sse_once 把这段响应挂到假服务上;临时目录则由 tempdir 创建。整个流程是在模拟一次用户真实执行命令,只是服务器响应被测试代码控制住了。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 2 个(tempdir, vec!)。

accepts_multiple_add_dir_flags42–72 ↗
async fn accepts_multiple_add_dir_flags() -> anyhow::Result<()>

作用:这个测试确认 --add-dir 可以重复写多次,而不只是支持一次或两次。它保护的是“多个额外目录都能被命令行接受”这个使用场景。

数据流:测试先搭好命令执行器和假的服务器,并让服务器准备返回“Multiple directories accepted”的完成消息。然后它创建三个临时目录,把每个目录都通过单独的 --add-dir 参数传给命令。命令运行时还带着跳过 Git 检查和 workspace-write 沙箱设置。最后测试只看一个关键结果:退出码是不是 0;如果是,就说明三个 --add-dir 参数一起出现时没有把命令弄崩。

调用关系:它和前一个测试类似,也是由异步测试框架启动,用假服务器代替真实后端。它同样调用 test_codex_execstart_mock_serverssemount_sse_oncetempdir 来搭建测试环境。区别是它把重点放在“重复参数”的情况上,用三个目录证明参数解析和执行启动流程能处理多个 --add-dir

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 2 个(tempdir, vec!)。

exec/tests/suite/agents_md.rs源码 ↗
testtest run

这个文件像一张验收清单,专门检查“项目说明文件”有没有正确进入请求。AGENTS.md 可以理解成给 AI 助手看的本项目工作守则,比如“怎么改代码”“注意哪些约定”。测试会先造一个临时工作目录,写入这些说明文件,再启动一个假的服务器来冒充真正的 AI 服务。接着它运行被测的 exec 命令,让命令把用户问题发出去。假的服务器会记录收到的请求,测试再从里面翻出用户消息,看里面有没有应该出现的指令。第一个测试确认普通 AGENTS.md 会被带上;第二个测试确认 AGENTS.override.md 优先级更高:有 override 时,只带 override 的内容,不带被遮住的 AGENTS.md 内容。这样能防止真实使用时 AI 没读到项目规则,或者同时读到互相冲突的规则。

函数细节2
exec_includes_workspace_agents_md_in_request7–34 ↗
async fn exec_includes_workspace_agents_md_in_request() -> anyhow::Result<()>

作用:这个测试确认:当工作目录里只有 AGENTS.md 时,exec 命令发给服务端的请求里会包含这个文件的内容。有人改到请求拼装逻辑时,这个测试能立刻发现“项目说明丢了”的问题。

数据流:一开始,它创建一个测试用的 exec 环境,并在临时工作目录写入 AGENTS.md,内容是“workspace instructions”。然后它启动一个模拟服务器,准备一段假的流式响应(流式响应就是服务端一段一段返回内容)。接着它运行 exec 命令,传入测试服务器和一句用户问题。命令跑完后,测试从模拟服务器记录的那一次请求里取出用户消息,检查其中至少有一条包含 AGENTS.md 的文字。结果是测试通过或失败;它还会在磁盘临时目录里写入这个测试文件。

调用关系:它是测试框架在跑测试时直接调用的异步测试。它先用 test_codex_exec 搭好被测命令环境,用 start_mock_server 开一个假服务端,用 sse 和 mount_sse_once 准备服务端回包,再通过命令执行路径触发真实请求。最后它不关心模型回复内容,只检查 mount_sse_once 记录下来的请求,确认 AGENTS.md 的内容确实被送出去了。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 3 个(assert!, write, vec!)。

exec_prefers_workspace_agents_override_md37–74 ↗
async fn exec_prefers_workspace_agents_override_md() -> anyhow::Result<()>

作用:这个测试确认:如果工作目录里同时有 AGENTS.md 和 AGENTS.override.md,exec 命令应该优先使用 AGENTS.override.md。这样可以保证临时覆盖规则真的生效,而不会和旧规则混在一起。

数据流:它先创建测试环境,在临时工作目录分别写入 AGENTS.md(“base instructions”)和 AGENTS.override.md(“override instructions”)。然后它启动模拟服务器,挂上一份假的流式响应,再运行 exec 命令并发送一句用户问题。执行结束后,它读取模拟服务器收到的用户消息:一方面确认消息里包含 override 文件的内容,另一方面确认所有用户消息里都不包含 base 文件的内容。最后输出的是测试成功或失败;过程中会写入两个本地临时说明文件,并让模拟服务器保存请求记录。

调用关系:它也是由测试框架自动运行的异步测试,流程和另一个测试类似:用 test_codex_exec 准备命令,用 start_mock_server、sse、mount_sse_once 搭好假的服务端和响应。不同点是它故意制造两个说明文件,借助模拟服务器记录的请求来验证优先级规则:AGENTS.override.md 会遮住 AGENTS.md。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 3 个(assert!, write, vec!)。

exec/tests/suite/auth_env.rs源码 ↗
testtest run

这份测试像一次“假装联网”的演练。它先启动一个假的服务器,用来冒充真正的 Codex 服务;然后告诉这个假服务器:只有收到 Authorization: Bearer dummy 这个请求头,才算请求正确。Authorization 可以理解成进门时出示的通行证,Bearer dummy 就是这次测试用的假钥匙。接着测试运行 codex exec 命令,让它执行一句简单的 echo testing codex api key。如果命令真的从环境或测试准备好的配置里拿到了 API Key,并把它放进请求头,假服务器就返回一个“完成”的事件,命令也会成功退出。反过来,如果请求没带钥匙、带错了,假服务器就匹配不上,测试会失败。这个文件不测试模型能力,只测试“命令行工具发请求时有没有正确带身份凭证”这件基础但关键的事。

函数细节1
exec_uses_codex_api_key_env_var10–31 ↗
async fn exec_uses_codex_api_key_env_var() -> anyhow::Result<()>

作用:这个测试函数确认 codex exec 会使用测试环境中的 Codex API Key,并把它变成 HTTP 请求里的 Authorization 请求头。有人改动鉴权代码时,这个测试能及时发现“钥匙没带上”这类问题。

数据流:进去的是测试辅助工具、一个新启动的假服务器,以及当前仓库根目录路径。函数先让假服务器准备好:只接受带有 Authorization: Bearer dummy 的请求,并返回一个表示任务完成的模拟事件。然后它启动测试版的 codex exec 命令,把假服务器地址和仓库目录交给它,再执行一条简单命令。出来的结果是命令必须成功结束;同时,假服务器也间接证明了请求头里的 API Key 是对的。

调用关系:这个函数由 Tokio 测试运行器在测试阶段自动执行。它先调用 test_codex_exec 准备一个可运行的测试命令,再调用 start_mock_server 开一个假服务;随后用 mount_sse_once_matchheadersseev_completed 设置服务端预期响应,最后通过测试命令发起实际执行流程。整个故事是:搭舞台、规定通行证、运行命令、确认命令成功通过检查。

调用图:调用 4 个内部函数(mount_sse_once_match, sse, start_mock_server, test_codex_exec);外部调用 3 个(repo_root, vec!, header)。

exec/tests/suite/originator.rs源码 ↗
testtest run

这个测试文件只在非 Windows 系统上运行。它会启动一个假的服务器,让 codex-exec 以为自己正在和真实服务通信,然后检查命令发出的 HTTP 请求里有没有正确的 Originator 头。第一项测试确认默认情况下来源名是 codex_exec;第二项测试确认如果设置了环境变量,就能把来源名改成指定值。这里的 SSE(Server-Sent Events,服务器连续推送事件的一种格式)用来模拟服务端正常返回“创建响应、发一条助手消息、完成响应”的流程。这样测试既不用真的联网,也能确认命令行工具和服务端约定的身份标记没有坏掉。

函数细节2
send_codex_exec_originator12–31 ↗
async fn send_codex_exec_originator() -> anyhow::Result<()>

作用:这个测试确认:在没有手动覆盖来源名时,codex-exec 发给服务器的请求会带上默认的 Originator: codex_exec。如果这个默认头丢了,服务端就可能分不清请求来自哪个工具。

数据流:测试先创建一个 codex-exec 的测试命令对象,再启动一个假的服务器。它准备好一段模拟的 SSE 返回内容,并要求服务器只接受带有 Originator 值为 codex_exec 的请求。然后它运行命令,移除覆盖来源名的环境变量,传入参数,最后检查命令正常退出,退出码是 0。

调用关系:这个函数是测试框架直接运行的异步测试。它用 test_codex_exec 准备被测命令,用 start_mock_server 开假服务器,用 sse 组装假响应,再把“必须匹配某个请求头”的规则交给 mount_sse_once_matchheader 用来描述要检查的请求头条件。

调用图:调用 4 个内部函数(mount_sse_once_match, sse, start_mock_server, test_codex_exec);外部调用 2 个(vec!, header)。

supports_originator_override34–54 ↗
async fn supports_originator_override() -> anyhow::Result<()>

作用:这个测试确认:如果通过环境变量指定了新的来源名,codex-exec 会真的改用这个值。这样内部测试、特殊部署或调试场景就可以清楚标记请求来源。

数据流:测试先创建命令对象和假服务器,再准备一段模拟的 SSE 成功返回。它要求服务器只接受 Originator 等于 codex_exec_override 的请求。接着运行 codex-exec,设置环境变量 CODEX_INTERNAL_ORIGINATOR_OVERRIDEcodex_exec_override,传入参数,最后确认命令成功结束,退出码是 0。

调用关系:这个函数同样由测试框架直接执行。它和默认来源名测试走的是同一套流程:test_codex_exec 生成命令,start_mock_server 提供假服务,sse 生成假响应,mount_sse_once_match 安装请求匹配规则,header 指明这次要检查的是覆盖后的请求头值。

调用图:调用 4 个内部函数(mount_sse_once_match, sse, start_mock_server, test_codex_exec);外部调用 2 个(vec!, header)。

exec/tests/suite/output_schema.rs源码 ↗
testtest execution

这个测试像一次“假演练”。它先准备一个很简单的 JSON Schema(JSON 的格式说明书,告诉程序输出必须长什么样),写到临时的 schema.json 文件里。然后它启动一个假的服务器,假装自己是后端模型服务,并准备好一段模拟回复。接着测试运行真正的 codex exec 命令,传入 --output-schema、模型名、工作目录和一句提示词。命令跑完后,测试不会只看它是否成功退出,还会检查刚才发给假服务器的请求内容。重点是看请求里的 text.format 是否包含固定名字、json_schema 类型、严格模式,以及刚才那份 schema。换句话说,这个文件不是测试模型回答得好不好,而是测试“用户交给命令行的输出格式要求,有没有被正确装进发往服务端的请求里”。

函数细节1
exec_includes_output_schema_in_request9–62 ↗
async fn exec_includes_output_schema_in_request() -> anyhow::Result<()>

作用:这个测试函数检查:当用户运行命令并指定 --output-schema schema.json 时,程序是否把这份 schema 放进发给服务器的请求里。有人修改命令行参数处理或请求组装代码时,它能及时发现“格式要求没传出去”的问题。

数据流:进去的是测试自己创建的临时工作目录、一份写入磁盘的 schema.json、一个假服务器,以及一次模拟的模型回复。函数先把 schema 写成文件,再启动假服务器并挂上一条只用一次的流式响应;随后运行命令行程序,把 schema 路径、模型名和提示词传进去。命令成功后,它取出假服务器收到的唯一请求,把请求体解析成 JSON,检查里面的 text.format 是否正好等于预期结构。出来的结果是测试通过或失败;它还在测试过程中创建了一个 schema 文件,并让假服务器记录了一次请求。

调用关系:它是这个测试文件的核心,也是唯一的测试入口,由 Tokio 测试框架在测试运行时自动调用。它先通过 test_codex_exec 搭好一个可运行命令的测试环境,再用 start_mock_server 启动假后端,用 ssemount_sse_once 准备模拟返回。最后它把实际命令跑起来,并用断言检查请求内容是否符合预期。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 5 个(assert_eq!, json!, to_vec_pretty, write, vec!)。

exec/tests/suite/prompt_stdin.rs源码 ↗
testtest suite / CI

这个文件是一组自动化测试,只在非 Windows 系统上运行。它关心的是命令行里最容易让人迷糊的一件事:用户到底是把提示词写在命令参数里,还是通过管道从标准输入传进来。标准输入可以理解成“从外面倒进程序的一段文字”,比如 echo hello | codex exec 里的 hello。这些测试会启动一个假的服务器,假装是后端模型服务,然后运行真实的 codex exec 命令,看它发给服务器的用户消息是不是符合预期。重点规则有几条:有命令行提示词且标准输入不为空时,要把两者拼在一起,并用 <stdin>...</stdin> 标出来;有提示词但标准输入为空时,不要多加东西;提示词写成 - 或者完全没写提示词时,就把标准输入当成提示词;如果这种情况下标准输入也是空的,就要失败并提示“没有从 stdin 提供提示词”。

函数细节6
exec_appends_piped_stdin_to_prompt_argument9–40 ↗
async fn exec_appends_piped_stdin_to_prompt_argument() -> anyhow::Result<()>

作用:这个测试确认:如果用户既在命令行里写了提示词,又通过管道传了内容,程序会把管道内容追加到提示词后面。这样用户可以先写任务说明,再把文件内容或命令输出一起交给模型。

数据流:测试先创建一个测试用的 codex exec 命令,再启动一个假的服务器,并准备一段假的服务器回复。然后它运行命令,传入提示词 Summarize this concisely,同时往标准输入里写入 my output\n。命令成功后,测试取出程序发给假服务器的请求,检查用户消息是否变成了“原提示词 + 空行 + <stdin> 包起来的输入内容”。

调用关系:它通过 test_codex_exec 搭好可运行的测试命令,通过 start_mock_server 启动假服务器,通过 ssemount_sse_once 准备服务器只回复一次的模拟结果。最后用断言检查请求内容,证明 codex exec 的拼接行为正确。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 2 个(assert!, vec!)。

exec_ignores_empty_piped_stdin_when_prompt_argument_is_present43–73 ↗
async fn exec_ignores_empty_piped_stdin_when_prompt_argument_is_present() -> anyhow::Result<()>

作用:这个测试确认:如果用户已经在命令行里写了提示词,但管道传进来的是空内容,程序不会额外加一段空的 stdin 标记。这样输出给模型的提示词会保持干净。

数据流:测试搭好命令和假服务器后,运行带提示词的 codex exec,但给标准输入写入空字符串。命令成功后,它查看发给服务器的请求,确认用户消息仍然只有 Summarize this concisely,没有多出来的空行、标签或空内容。

调用关系:它和前一个测试使用同一套测试支架:test_codex_exec 负责创建命令,start_mock_serverssemount_sse_once 负责模拟后端响应。这个测试补上了“有提示词但 stdin 为空”的边界情况,防止拼接逻辑过度处理。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 2 个(assert!, vec!)。

exec_dash_prompt_reads_stdin_as_the_prompt76–107 ↗
async fn exec_dash_prompt_reads_stdin_as_the_prompt() -> anyhow::Result<()>

作用:这个测试确认:当用户把提示词参数写成 - 时,程序会按老规矩从标准输入读取真正的提示词。这里的 - 就像命令行里的一个约定暗号,意思是“内容从 stdin 来”。

数据流:测试运行 codex exec ... -,并往标准输入写入 prompt from stdin\n。命令成功后,它检查假服务器收到的用户消息,确认内容正好就是标准输入里的文字,没有再加其他包装。

调用关系:它使用 test_codex_exec 运行命令,用 start_mock_serverssemount_sse_once 提供假的模型回复。它验证的是 - 这个特殊参数的路径,确保新增的 stdin 拼接规则没有破坏原本“强制从 stdin 读提示词”的行为。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 2 个(assert!, vec!)。

exec_without_prompt_argument_reads_piped_stdin_as_the_prompt110–140 ↗
async fn exec_without_prompt_argument_reads_piped_stdin_as_the_prompt() -> anyhow::Result<()>

作用:这个测试确认:如果用户完全没有在命令行里写提示词,但通过管道传了内容,程序会把这段管道内容当成提示词。这样 echo '问题' | codex exec 这种用法可以正常工作。

数据流:测试运行 codex exec,不传最后的提示词参数,只往标准输入写入 prompt from stdin\n。命令结束后,它拿到发给假服务器的请求,确认用户消息就是这段标准输入内容。

调用关系:它同样借助 test_codex_exec 创建命令,并让 start_mock_serverssemount_sse_once 组成一个可验证的假后端。它覆盖的是“没有提示词参数”的路径,保证程序仍然支持从管道直接拿提示词。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 2 个(assert!, vec!)。

exec_without_prompt_argument_rejects_empty_piped_stdin143–155 ↗
fn exec_without_prompt_argument_rejects_empty_piped_stdin()

作用:这个测试确认:如果用户既没写提示词,标准输入也是空的,程序必须失败并说明原因。否则程序可能会拿着空问题去请求模型,让用户不知道哪里错了。

数据流:测试创建 codex exec 命令,不传提示词参数,同时写入空的标准输入。它不启动假服务器,因为这种情况应该在本地就被拦下来。最后它检查退出码是 1,并且标准错误输出里包含 No prompt provided via stdin.

调用关系:它通过 test_codex_exec 构造命令,用 contains 检查错误文本。这个测试守住了输入校验这一关:在没有任何提示词来源时,程序应该尽早报错,而不是继续往后执行。

调用图:调用 1 个内部函数(test_codex_exec);外部调用 1 个(contains)。

exec_dash_prompt_rejects_empty_piped_stdin158–171 ↗
fn exec_dash_prompt_rejects_empty_piped_stdin()

作用:这个测试确认:如果用户写了 -,表示要从标准输入读提示词,但标准输入其实是空的,程序也必须失败。这样可以避免用户以为自己传了内容,实际却什么都没传。

数据流:测试运行带 - 参数的 codex exec,同时给标准输入写入空字符串。它检查程序退出码为 1,并且错误信息包含 No prompt provided via stdin.,说明程序明确告诉用户 stdin 里没有提示词。

调用关系:它使用 test_codex_exec 启动命令,用 contains 匹配错误输出。它和“没有提示词参数且 stdin 为空”的测试一起,覆盖了两种应该拒绝空输入的情况,确保 - 这个特殊入口也不会放过空提示词。

调用图:调用 1 个内部函数(test_codex_exec);外部调用 1 个(contains)。

执行模式与生命周期

这些测试检验 codex-exec 二进制文件界面中的审批行为、会话持久化与恢复、hooks、支持补丁的流程,以及启动/运行时失败退出。

exec/tests/suite/approval_policy.rs源码 ↗
testtest execution

这个测试文件只在非 Windows 系统上跑。它模拟一次完整的 exec 命令执行:先写入一份配置文件,里面说审批策略是 on-request,也就是“遇到需要批准的事再问人”,并指定用 auto_review,也就是“自动审查器”。然后它启动一个假的服务器,假装后端模型已经返回了“done”。接着真正运行 exec 命令,看命令输出到 stderr(标准错误输出,常用来打印运行状态)的审批模式是什么。三个测试分别覆盖默认情况、危险绕过模式、全自动模式:默认时应该保留 on-request;如果用户明确要求绕过审批或全自动,就应该变成 never,也就是“不再请求批准”。这相当于检查汽车的安全开关:默认不能偷偷改,用户明确切到自动驾驶时才允许改变。

函数细节4
run_exec_with_auto_review_config7–35 ↗
async fn run_exec_with_auto_review_config(extra_args: &[&str]) -> anyhow::Result<String>

作用:这是三个测试共用的“小剧场搭建器”。它准备临时配置、假服务器和命令参数,真正跑一次 exec,然后把 stderr 文本拿回来给测试检查。

数据流:进去的是一组额外命令行参数,比如空参数、绕过审批参数或全自动参数。它先创建测试用的 exec 环境,往测试 home 目录写 config.toml;再启动 mock server(假服务器,用来冒充真实后端),挂上一段 SSE 响应(Server-Sent Events,一种服务器连续推送消息的格式),内容表示模型创建响应、说了 done、然后完成。之后它带着这些参数运行 exec 命令,确认命令成功退出。最后把 stderr 从字节转成字符串并返回;如果写文件、运行命令或转文字失败,就返回错误。

调用关系:它是本文件的核心辅助函数,被三个具体测试调用。它自己把活儿分给 test_codex_exec 来创建测试命令环境,分给 start_mock_server、sse、mount_sse_once 来准备假后端响应,然后把运行结果交还给上层测试,让上层只关心 stderr 里有没有正确的审批模式文字。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);被 3 处调用(exec_bypass_preserves_never_for_auto_review_config, exec_full_auto_preserves_never_for_auto_review_config, exec_preserves_on_request_for_auto_review_config);外部调用 4 个(from_utf8, assert!, write, vec!)。

exec_preserves_on_request_for_auto_review_config38–46 ↗
async fn exec_preserves_on_request_for_auto_review_config() -> anyhow::Result<()>

作用:这个测试确认:只有配置 auto_review、没有额外强制参数时,exec 不应该把审批策略偷偷改掉。它应该显示 approval: on-request。

数据流:它不给辅助函数传额外参数。辅助函数跑完 exec 后返回 stderr 文本。这个测试检查文本里是否包含 approval: on-request;包含就通过,不包含就失败,并把实际 stderr 打出来方便排查。

调用关系:它是默认场景的验证者。它调用 run_exec_with_auto_review_config 搭好环境并执行命令,自己只负责判断结果是否符合“默认保留配置”的规则。

调用图:调用 1 个内部函数(run_exec_with_auto_review_config);外部调用 1 个(assert!)。

exec_bypass_preserves_never_for_auto_review_config49–58 ↗
async fn exec_bypass_preserves_never_for_auto_review_config() -> anyhow::Result<()>

作用:这个测试确认:用户使用危险绕过参数时,审批模式应该是 never,也就是不再请求批准。它防止 auto_review 配置把这个明确选择冲掉。

数据流:它把 --dangerously-bypass-approvals-and-sandbox 这个额外参数传进去。辅助函数用这个参数运行 exec,并返回 stderr。测试随后检查 stderr 是否包含 approval: never;如果没有,就说明绕过模式没有正确生效。

调用关系:它覆盖“用户明确要求绕过审批和沙箱”的场景。它依赖 run_exec_with_auto_review_config 完成实际执行,自己负责确认最终显示出来的审批策略是 never。

调用图:调用 1 个内部函数(run_exec_with_auto_review_config);外部调用 1 个(assert!)。

exec_full_auto_preserves_never_for_auto_review_config61–69 ↗
async fn exec_full_auto_preserves_never_for_auto_review_config() -> anyhow::Result<()>

作用:这个测试确认:用户使用 full-auto 全自动模式时,审批模式也应该是 never。它确保全自动设置不会被配置文件里的 on-request 和 auto_review 搞乱。

数据流:它把 --full-auto 作为额外命令行参数传给辅助函数。辅助函数运行 exec 后返回 stderr。测试检查 stderr 中有没有 approval: never;有就说明全自动模式正确把审批请求关掉了,没有就失败。

调用关系:它覆盖“全自动运行”的场景。和另外两个测试一样,它把复杂的环境准备交给 run_exec_with_auto_review_config,自己只检查全自动模式下最终审批状态是否正确。

调用图:调用 1 个内部函数(run_exec_with_auto_review_config);外部调用 1 个(assert!)。

exec/tests/suite/ephemeral.rs源码 ↗
testtest run

这个测试文件解决的是一个很实际的问题:用户如果选择“临时模式”,通常就是不想留下本次对话或执行记录;如果程序还是偷偷写文件,就会违背这个承诺。文件里用一个假的网络服务来冒充后端,让命令以为自己收到了正常回复,这样测试不用依赖真实服务。它先准备一段模拟的 SSE 响应,SSE 可以理解成服务器一条条推送消息的格式。然后运行 codex exec,分别测试两种情况:默认情况下,运行完应该在测试用的 home 目录里产生一个 .jsonl 会话文件;加上 --ephemeral 后,运行完应该一个也不产生。这里还特意不在 Windows 上跑,因为文件路径或运行环境行为可能不同。

函数细节4
exec_sse_response10–16 ↗
fn exec_sse_response() -> String

作用:这个函数造出一份假的服务器回复,用来让测试里的 codex exec 觉得后端正常返回了结果。它避免测试真的去请求线上服务。

数据流:进去没有参数 → 它把“创建响应”“助手消息”“完成响应”这几段事件拼成一段 SSE 文本 → 出来一个字符串,内容是一整套模拟的流式回复。

调用关系:两个测试都会先调用它拿到假回复,然后把这份回复交给 mount_sse_once 挂到假的服务器上。这样后面的命令运行时,就能收到可预测的结果。

调用图:调用 1 个内部函数(sse);被 2 处调用(does_not_persist_rollout_file_in_ephemeral_mode, persists_rollout_file_by_default);外部调用 1 个(vec!)。

session_rollout_count18–30 ↗
fn session_rollout_count(home_path: &std::path::Path) -> usize

作用:这个函数数一数测试 home 目录下到底留下了多少个会话记录文件。它是判断“有没有持久保存”的尺子。

数据流:进去一个 home 目录路径 → 它进入这个目录下的 sessions 子目录,如果目录不存在就直接认为是 0 → 如果存在,就递归查看里面所有文件,只统计文件名以 .jsonl 结尾的文件 → 出来一个数量。

调用关系:测试命令跑完后会调用它检查结果。默认模式的测试希望它数到 1,临时模式的测试希望它数到 0。

调用图:外部调用 2 个(join, new)。

persists_rollout_file_by_default33–48 ↗
async fn persists_rollout_file_by_default() -> anyhow::Result<()>

作用:这个测试确认默认运行 codex exec 时,会把本次会话保存成文件。它防止有人改代码时不小心把默认保存行为弄坏。

数据流:开始时先检查网络条件,不合适就跳过 → 创建一个测试用的命令环境和一个假的服务器 → 用 exec_sse_response 准备假回复,并挂到服务器上 → 运行 codex exec --skip-git-repo-check default persistence behavior → 要求命令退出码是 0 → 最后数会话文件,确认数量是 1。

调用关系:它是完整流程测试的驾驶员:先用 test_codex_exec 搭测试环境,再用 MockServer::start 开假服务器,用 mount_sse_once 安排服务器回复,最后交给 session_rollout_count 验证磁盘上是否真的留下记录。

调用图:调用 3 个内部函数(mount_sse_once, test_codex_exec, exec_sse_response);外部调用 3 个(start, assert_eq!, skip_if_no_network!)。

does_not_persist_rollout_file_in_ephemeral_mode51–67 ↗
async fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()>

作用:这个测试确认加上 --ephemeral 后,codex exec 不会保存会话记录。它保护的是临时模式最核心的承诺:跑完不留会话文件。

数据流:开始时先检查网络条件,不合适就跳过 → 创建测试环境和假的服务器 → 生成并挂载一份模拟回复 → 运行 codex exec --skip-git-repo-check --ephemeral ephemeral behavior → 要求命令成功退出 → 最后检查测试 home 目录里的 .jsonl 会话文件数量,必须是 0。

调用关系:它和默认保存测试形成对照:两边使用同样的假服务器和同样的回复准备方式,唯一关键差别是这里多传了 --ephemeral。正是这个差别应该让后面的 session_rollout_count 从 1 变成 0。

调用图:调用 3 个内部函数(mount_sse_once, test_codex_exec, exec_sse_response);外部调用 3 个(start, assert_eq!, skip_if_no_network!)。

exec/tests/suite/hooks.rs源码 ↗
testtest execution

这个测试只在非 Windows 系统上跑,因为它用到了 touch 这种常见的 Unix 命令。它做的事很像检查“开门铃”:先在临时家目录里写一个 hooks.json,里面说会话开始时要执行一条命令,这条命令会创建一个叫 session-start-ran 的标记文件。然后它启动一个假的服务器,假装远端模型正常返回了“done”。接着测试用命令行方式运行程序,并加上 --dangerously-bypass-hook-trust,意思是明确允许绕过钩子信任保护。最后它检查那个标记文件是否存在。如果存在,就说明会话开始钩子确实跑了;如果不存在,测试失败。这个文件重要在于它验证了一个带风险但有意提供的功能:用户选择绕过保护时,程序必须按配置执行钩子。

函数细节1
exec_hook_trust_bypass_runs_session_start_hook8–43 ↗
async fn exec_hook_trust_bypass_runs_session_start_hook() -> anyhow::Result<()>

作用:这个测试函数确认:在加了 --dangerously-bypass-hook-trust 这个危险但明确的命令行选项后,会话启动钩子会真正执行。它通过看一个标记文件有没有被创建,来判断钩子命令是否跑过。

数据流:进去的是一个测试用的临时运行环境,以及一个模拟远端服务的假服务器。函数先算出标记文件路径,再写入 hooks.json,让会话开始时执行 touch 标记文件。然后它准备好假的服务器响应,运行被测试的命令行程序,并传入跳过 Git 仓库检查和绕过钩子信任检查的参数。出来的结果是:命令必须成功结束,并且磁盘上必须出现标记文件;否则测试报错。

调用关系:它是这个测试文件的唯一入口,由 Rust 的异步测试框架自动调用。它先用 test_codex_exec 搭好隔离的测试程序环境,再用 start_mock_serverssemount_sse_once 准备假的模型返回,最后通过测试命令真正跑一次程序。整个流程是在模拟一次真实用户启动会话,从而验证启动钩子这条链路没有断。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 6 个(assert!, format!, json!, to_vec_pretty, write, vec!)。

exec/tests/suite/mcp_required_exit.rs源码 ↗
testtest

这份测试模拟了一个很常见也很危险的情况:用户在配置文件里要求启动一个必需的 MCP 服务器。MCP 可以理解成一种让主程序连接外部工具或服务的接口。测试故意把服务器命令写成一个不存在的程序名,这样它一定启动不了。接着,测试又搭了一个假的后端服务器,让主程序还能收到正常的回答,避免失败原因被别的网络问题干扰。最后它运行命令行程序,并检查两件事:退出码必须是 1,表示程序失败;错误输出里必须明确写出哪个必需的 MCP 服务器没初始化成功。这个测试的重要点是:即使主流程看起来还能继续,必需依赖坏了也必须让用户马上知道,不能悄悄降级。

函数细节1
exits_non_zero_when_required_mcp_server_fails_to_initialize9–38 ↗
async fn exits_non_zero_when_required_mcp_server_fails_to_initialize() -> anyhow::Result<()>

作用:这条测试确认:当一个标记为必需的 MCP 服务器启动失败时,命令行程序会用非零退出码结束,并把失败的服务器名字写到错误信息里。非零退出码就是告诉操作系统和脚本“这次运行失败了”。

数据流:测试先创建一个临时的命令行运行环境,然后往它的家目录里写入一份配置文件,里面声明一个必需的 MCP 服务器,但启动命令是假的、一定找不到。接着它启动一个模拟服务器,并准备一段假的正常响应数据。最后它运行被测命令,传入参数和提示词,检查结果:程序退出码变成 1,标准错误输出里包含“required_broken”这个失败服务器名。

调用关系:这条测试是整个场景的导演。它用 test_codex_exec 准备可测试的程序环境,用 std::fs::write 写配置文件,用 responses::start_mock_server 启动假的后端服务,用 responses::sse 和 responses::mount_sse_once 准备一次模拟响应,再用 predicates::str::contains 检查错误文本。它不测试网络回复本身,而是借这些工具把环境搭好,专门验证“必需 MCP 启动失败时必须失败退出”这个规则。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 3 个(contains, write, vec!)。

exec/tests/suite/apply_patch.rs源码 ↗
testtest execution

这个测试文件像一套验收题,检查 codex-exec 的 apply_patch 能力有没有坏掉。apply_patch 可以理解成“按说明书修改文件”:补丁里写着新增哪行、删掉哪行、改哪个文件,程序照着做。第一个测试直接启动 codex-exec,传入一个补丁,确认它能把临时文件从原内容改成新内容,并且输出成功信息。后两个测试更接近真实使用场景:先启动一个假的服务器,模拟模型通过 SSE(服务器持续推送消息的一种方式)发来“请应用这个补丁”的工具调用,然后运行 codex-exec,最后检查工作目录里的文件是否真的被创建、更新。这里还特意测了普通补丁和比较自由格式的补丁,防止补丁格式稍微复杂时出错。Windows 上跳过后两个测试,因为这些集成测试只在非 Windows 系统启用。

函数细节3
test_standalone_exec_cli_can_use_apply_patch19–45 ↗
fn test_standalone_exec_cli_can_use_apply_patch() -> anyhow::Result<()>

作用:这个测试确认独立的 codex-exec 命令行工具,不靠完整的 codex 多功能命令,也能像 apply_patch 工具一样修改文件。这样可以防止以后改命令行时,把这个轻量入口的补丁能力弄坏。

数据流:它先创建一个临时文件夹,并在里面写入 source.txt,内容是 original content。然后它启动编译出来的 codex-exec,把 apply_patch 的特殊参数和补丁文本传进去,并把当前目录设为临时文件夹。程序运行结束后,测试检查三件事:命令成功退出,标准输出说文件已更新,标准错误没有内容;最后再读回 source.txt,确认内容已经变成 modified by apply_patch

调用关系:这是最直接的一条测试路径,不需要假服务器,也不模拟模型响应。它通过 cargo_bin 找到 codex-exec 可执行文件,用 Command::new 启动它,用 tempdirfs::write 准备现场,最后用断言检查命令输出和文件结果。

调用图:外部调用 6 个(assert_eq!, new, cargo_bin, write, is_empty, tempdir)。

test_apply_patch_tool49–93 ↗
async fn test_apply_patch_tool() -> anyhow::Result<()>

作用:这个测试确认在正常的执行流程里,模型发来的 apply_patch 工具调用会真的落到本地文件系统上。它模拟模型先创建文件、再修改文件,最后检查文件内容是不是最终版本。

数据流:它先准备一个测试用的 codex-exec 运行环境和临时工作目录。接着准备两段补丁:第一段新增 test.md,写入 Hello world;第二段把它改成 Final text。然后它启动一个假服务器,把三段 SSE 响应挂上去:第一次要求应用新增补丁,第二次要求应用更新补丁,第三次表示完成。测试运行 codex-exec 并连接这个假服务器,等命令成功后读取 test.md,确认最后内容是 Final text\n

调用关系:这个测试处在更完整的集成流程里:start_mock_server 先搭一个假的后端,mount_sse_sequence 把预设的模型消息装进去,cmd_with_server 再让 codex-exec 去和这个假后端交互。过程中,模拟出来的 ev_apply_patch_custom_tool_call 让程序触发补丁工具,最后测试只关心磁盘文件是否被正确改好。

调用图:调用 2 个内部函数(mount_sse_sequence, start_mock_server);外部调用 4 个(assert_eq!, skip_if_no_network!, read_to_string, vec!)。

test_apply_patch_freeform_tool97–147 ↗
async fn test_apply_patch_freeform_tool() -> anyhow::Result<()>

作用:这个测试检查更宽松、更不规整的补丁文本也能被 apply_patch 正确处理。它特别用一段 Python 文件补丁来验证:即使补丁里有空行和缩进变化,最终文件也要符合预期。

数据流:它先准备测试环境,然后构造两段补丁:第一段新增 app.py,里面有一个类和返回 False 的方法;第二段把方法里的返回值改成 True,并带有额外空行这类更自由的格式。接着它启动假服务器,把这些补丁按顺序作为 SSE 响应发给 codex-exec。命令成功运行后,测试读取生成的 app.py,并把它和 fixture 文件 apply_patch_freeform_final.txt 中保存的标准答案逐字比较。

调用关系:它和 test_apply_patch_tool 走的是同一种集成测试路线:先用 start_mock_server 搭假服务器,再用 mount_sse_sequence 安排模型消息,最后运行 codex-exec。区别是这里测试的是更“自由写法”的补丁,所以最后不是只比较一行文字,而是拿完整的预期文件内容来对照。

调用图:调用 2 个内部函数(mount_sse_sequence, start_mock_server);外部调用 4 个(assert_eq!, skip_if_no_network!, read_to_string, vec!)。

exec/tests/suite/resume.rs源码 ↗
testtest execution

可以把这里的测试理解成给“继续上一段聊天记录”功能做体检。程序每次运行会把对话写进一个会话文件,resume 就应该找到正确的旧文件,然后把新的提问追加进去,而不是新建一份或写错地方。这个文件会启动一个假的网络服务器,模拟 AI 服务返回结果,这样测试不用真的依赖外部接口。它还会在临时目录里创建不同工作目录、图片文件、JSON 输出格式文件,再运行真实的命令行程序,检查最后保存下来的会话文件内容。重点覆盖几类情况:按最后一次会话继续、按会话 ID 继续、带 --json 和全局参数继续、当前目录过滤是否正确、输出 schema 是否随请求发出、图片附件是否保存。辅助函数负责找会话文件、读出会话 ID、数最后一条用户消息里的图片数量,让测试能像用户一样从外部观察结果。

函数细节14
find_session_file_containing_marker16–61 ↗
fn find_session_file_containing_marker(
    sessions_dir: &std::path::Path,
    marker: &str,
) -> Option<std::path::PathBuf>

作用:在会话目录里找出哪个 .jsonl 会话文件包含某个独一无二的标记文字。测试用它确认一次命令运行到底写进了哪份会话记录。

数据流:输入是会话目录路径和一段标记文字 → 它递归翻看目录里的 .jsonl 文件,跳过第一行元信息,逐行解析 JSON,查找 AI 或用户消息内容里是否包含这个标记 → 找到就返回这个文件的完整路径,找不到就返回空值;它只读取文件,不修改任何东西。

调用关系:多个 resume 测试在第一次运行后用它定位原始会话文件,在第二次运行后再用它找新标记出现在哪里。这样测试就能判断“继续会话”是否真的追加到了同一个文件。

调用图:被 6 处调用(exec_resume_accepts_images_after_subcommand, exec_resume_by_id_appends_to_existing_file, exec_resume_last_accepts_prompt_after_flag_in_json_mode, exec_resume_last_appends_to_existing_file, exec_resume_last_respects_cwd_filter_and_all_flag, exec_resume_preserves_cli_configuration_overrides);外部调用 3 个(new, from_str, read_to_string)。

extract_conversation_id64–74 ↗
fn extract_conversation_id(path: &std::path::Path) -> String

作用:从会话文件第一行的元信息里取出这段对话的 ID。测试用它模拟用户拿着某个会话编号去继续指定会话。

数据流:输入是一份会话文件路径 → 它读取文件内容,拿第一行当作 JSON 解析,再从里面取 payload.id → 输出一个字符串形式的会话 ID;如果字段不存在,就输出空字符串。

调用关系:按 ID 恢复会话的测试会先用 find_session_file_containing_marker 找到文件,再用它取出 ID,然后把这个 ID 交给 resume 命令。目录过滤测试也用它把某个会话明确“点名”续写一次。

调用图:被 2 处调用(exec_resume_by_id_appends_to_existing_file, exec_resume_last_respects_cwd_filter_and_all_flag);外部调用 2 个(from_str, read_to_string)。

last_user_image_count76–107 ↗
fn last_user_image_count(path: &std::path::Path) -> usize

作用:数一数某个会话文件里最后一条用户消息带了几张图片。它用来确认 resume 后传入的图片附件没有丢。

数据流:输入是一份会话文件路径 → 它逐行读取并解析 JSON,只关心类型是用户消息的记录,再查看消息内容数组里有多少项是 input_image → 输出最后一条用户消息中的图片数量;它不会改文件。

调用关系:图片附件测试在运行 resume --image ... 之后调用它,检查最终写进会话记录的用户消息确实包含两张图片。

调用图:被 1 处调用(exec_resume_accepts_images_after_subcommand);外部调用 2 个(from_str, read_to_string)。

exec_repo_root109–111 ↗
fn exec_repo_root() -> anyhow::Result<std::path::PathBuf>

作用:拿到这个项目仓库的根目录路径。测试需要一个稳定的工作目录,避免命令因为目录不对而表现不一致。

数据流:没有业务输入 → 它调用测试工具提供的仓库定位方法 → 输出仓库根目录路径,或者在找不到时返回错误。

调用关系:多个需要指定 -C 工作目录的测试都会先调用它,然后把返回路径传给命令行程序。它本身不跑命令,只是帮测试准备环境。

调用图:被 5 处调用(exec_resume_accepts_images_after_subcommand, exec_resume_by_id_appends_to_existing_file, exec_resume_last_accepts_prompt_after_flag_in_json_mode, exec_resume_last_appends_to_existing_file, exec_resume_preserves_cli_configuration_overrides);外部调用 1 个(repo_root)。

exec_sse_response113–121 ↗
fn exec_sse_response(index: usize) -> String

作用:生成一段假的流式 AI 响应文本。SSE 是“服务器持续推送事件”的格式,这里用它假装 AI 服务创建了一次回答、发了一条消息、然后完成。

数据流:输入是一个序号 → 它用序号拼出响应 ID 和消息 ID,再组装成三段事件:创建响应、助手消息、完成响应 → 输出一整段可被 mock 服务器返回的 SSE 字符串。

调用关系:它是 mount_exec_responses 的小零件。后者需要准备多次假的 AI 返回,就会反复用它生成每一次的响应内容。

调用图:调用 1 个内部函数(sse);外部调用 2 个(format!, vec!)。

mount_exec_responses123–128 ↗
async fn mount_exec_responses(
    server: &MockServer,
    count: usize,
) -> core_test_support::responses::ResponseMock

作用:把若干次假的 AI 响应挂到 mock 服务器上。这样测试运行真实命令时,请求会打到假服务器,并收到可预测的回答。

数据流:输入是 mock 服务器和需要准备的响应次数 → 它为每个序号生成一段 SSE 响应,再注册成按顺序返回的服务端回复 → 输出一个记录请求情况的 mock 对象,测试后面可以用它查看收到过哪些请求。

调用关系:几乎所有测试开始时都会调用它。它把网络部分准备好,后续 test.cmd_with_server 运行命令时就不会访问真实 AI 服务,而是走这里安排好的假响应。

调用图:调用 1 个内部函数(mount_sse_sequence);被 8 处调用(exec_resume_accepts_global_flags_after_subcommand, exec_resume_accepts_images_after_subcommand, exec_resume_by_id_appends_to_existing_file, exec_resume_includes_output_schema_in_request, exec_resume_last_accepts_prompt_after_flag_in_json_mode, exec_resume_last_appends_to_existing_file, exec_resume_last_respects_cwd_filter_and_all_flag, exec_resume_preserves_cli_configuration_overrides)。

exec_resume_last_appends_to_existing_file131–181 ↗
async fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()>

作用:验证最基本的 resume --last:继续最近一次会话时,应该追加到原来的会话文件,而不是另开新文件。

数据流:它先创建测试命令环境和假服务器,再运行一次带独特标记的 prompt → 用标记找到生成的会话文件 → 第二次运行 resume --last 并带另一个标记 → 再找新标记所在文件,最后断言它和旧文件是同一个,并且两个标记都在文件里。

调用关系:这是 resume 功能的核心回归测试。它依赖 mount_exec_responses 准备假响应,依赖 exec_repo_root 提供工作目录,依赖 find_session_file_containing_marker 从磁盘结果里判断命令行为是否正确。

调用图:调用 4 个内部函数(test_codex_exec, exec_repo_root, find_session_file_containing_marker, mount_exec_responses);外部调用 6 个(start, assert!, assert_eq!, format!, skip_if_no_network!, read_to_string)。

exec_resume_last_accepts_prompt_after_flag_in_json_mode184–234 ↗
async fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<()>

作用:验证在 JSON 输出模式下,prompt 放在 resume --last 后面也能被正确识别。它防止命令行参数顺序稍微变化就把用户输入吞掉。

数据流:它先正常运行一次,写入第一个独特标记并找到会话文件 → 第二次用 --json resume --last <prompt> 的顺序运行 → 查找第二个标记写到了哪里 → 输出结果是测试断言:同一个会话文件里同时包含两次 prompt 的标记。

调用关系:这个测试和基础 --last 测试很像,但重点放在参数解析。它同样用假服务器、仓库根目录和会话文件扫描工具来观察最终效果。

调用图:调用 4 个内部函数(test_codex_exec, exec_repo_root, find_session_file_containing_marker, mount_exec_responses);外部调用 6 个(start, assert!, assert_eq!, format!, skip_if_no_network!, read_to_string)。

exec_resume_last_respects_cwd_filter_and_all_flag237–339 ↗
async fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()>

作用:验证 resume --last 会优先找当前工作目录相关的最近会话,而 --all 会忽略这个目录限制、直接找全局最新会话。

数据流:它创建两个临时目录 A 和 B,分别运行命令生成两份会话 → 找到 B 的会话 ID,并把 B 再续写一次,让它变成更新的会话 → 从 A 目录运行 resume --last --all,确认会选到全局最新的 B → 再从 A 目录运行不带 --allresume --last,确认根据最新上下文仍选到匹配当前目录的会话。

调用关系:这是较复杂的选择规则测试。它用 extract_conversation_id 精确续写 B,用 find_session_file_containing_marker 判断哪份文件被追加,也用短暂睡眠避免时间戳同一秒导致排序不稳定。

调用图:调用 4 个内部函数(test_codex_exec, extract_conversation_id, find_session_file_containing_marker, mount_exec_responses);外部调用 7 个(start, new, assert_eq!, format!, skip_if_no_network!, sleep, from_millis)。

exec_resume_accepts_global_flags_after_subcommand342–376 ↗
async fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()>

作用:验证全局参数即使写在 resume 子命令后面也能生效。换句话说,用户不必严格记住所有参数必须放在命令最前面。

数据流:它先创建一个可恢复的会话 → 然后运行 resume --last,并在子命令后面放入配置、JSON 模式、模型名、推理等级、跳过沙箱审批等全局参数 → 只要命令成功退出,就说明参数解析接受这种写法。

调用关系:这个测试主要检查命令行解析器的兼容性。它用 mount_exec_responses 准备两次假响应,第一轮种下会话,第二轮验证带大量参数的恢复命令不会失败。

调用图:调用 2 个内部函数(test_codex_exec, mount_exec_responses);外部调用 3 个(start, format!, skip_if_no_network!)。

exec_resume_includes_output_schema_in_request379–432 ↗
async fn exec_resume_includes_output_schema_in_request() -> anyhow::Result<()>

作用:验证恢复会话时,如果用户指定了输出 schema,请求里仍然会带上这个格式要求。schema 可以理解成“答案必须长成什么样”的 JSON 模板。

数据流:它先写出一个 schema 文件,内容要求回答是带 answer 字段的 JSON 对象 → 创建一段可恢复会话 → 再用 resume --last --json --output-schema <文件> 运行 → 从 mock 服务器记录的第二次请求里取出 JSON 请求体,断言 text.format 正好包含这个 schema。

调用关系:这个测试不仅看会话文件,还检查发给 AI 服务的请求内容。mount_exec_responses 返回的 mock 对象会记录请求,测试最后从那里取第二次请求来比对。

调用图:调用 2 个内部函数(test_codex_exec, mount_exec_responses);外部调用 6 个(start, assert_eq!, json!, to_vec_pretty, skip_if_no_network!, write)。

exec_resume_by_id_appends_to_existing_file435–488 ↗
async fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()>

作用:验证用户明确指定会话 ID 时,resume 会把新内容追加到对应旧文件。它防止“按编号继续”误开新会话或选错会话。

数据流:它先运行一次命令,生成包含第一个标记的会话文件 → 从这个文件里提取会话 ID,并确认 ID 不为空 → 第二次运行 resume <session_id> 并带第二个标记 → 找到第二个标记所在文件,断言它就是原文件,并且两个标记都存在。

调用关系:这个测试把 find_session_file_containing_markerextract_conversation_id 串起来,先从磁盘记录拿到真实 ID,再把 ID 交回命令行程序,形成一次完整的“查 ID → 按 ID 继续”的流程。

调用图:调用 5 个内部函数(test_codex_exec, exec_repo_root, extract_conversation_id, find_session_file_containing_marker, mount_exec_responses);外部调用 6 个(start, assert!, assert_eq!, format!, skip_if_no_network!, read_to_string)。

exec_resume_preserves_cli_configuration_overrides491–563 ↗
async fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()>

作用:验证恢复会话时,用户这次在命令行里写的配置覆盖项仍然算数,比如模型名和沙箱模式。它防止旧会话配置把新命令参数盖掉。

数据流:它先用某个模型和沙箱设置创建会话 → 第二次 resume 时换成另一个模型配置,并捕获命令输出 → 检查命令成功,标准错误输出里显示的是这次传入的模型和沙箱信息 → 再确认新标记被追加到原来的会话文件中。

调用关系:这个测试同时检查两件事:会话确实续写到旧文件,以及配置显示/覆盖没有丢。它用 exec_repo_root 固定目录,用 find_session_file_containing_marker 验证文件,用命令输出文本验证配置。

调用图:调用 4 个内部函数(test_codex_exec, exec_repo_root, find_session_file_containing_marker, mount_exec_responses);外部调用 8 个(start, from_utf8, assert!, assert_eq!, cfg!, format!, skip_if_no_network!, read_to_string)。

exec_resume_accepts_images_after_subcommand566–623 ↗
async fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()>

作用:验证恢复会话时,--image 图片参数放在 resume 子命令后面也能正常加入用户消息。它确保多模态输入,也就是文字加图片的输入,不会因为 resume 而失效。

数据流:它先创建一个普通会话 → 在测试目录写出两张很小的 PNG 图片文件 → 第二次运行 resume --last --image 图1 --image 图2 <prompt> → 找到新 prompt 所在会话文件,再统计最后一条用户消息里的图片数量 → 断言数量是 2。

调用关系:这个测试用 mount_exec_responses 提供假 AI 回复,用 find_session_file_containing_marker 找到恢复后的文件,最后把检查交给 last_user_image_count,确认图片真的写入了会话记录。

调用图:调用 5 个内部函数(test_codex_exec, exec_repo_root, find_session_file_containing_marker, last_user_image_count, mount_exec_responses);外部调用 5 个(start, assert_eq!, format!, skip_if_no_network!, write)。

exec/tests/suite/server_error_exit.rs源码 ↗
testtest execution

这个测试只在非 Windows 系统上运行。它模拟了一个很简单的服务器:客户端一连上来,服务器就通过 SSE(Server-Sent Events,一种服务器持续往客户端推送消息的方式)发回一个“response.failed”的失败事件,里面带着“限流”之类的错误信息。然后测试启动 codex-exec,让它连接这个假服务器,并执行一句普通请求。关键检查点不是错误文字本身,而是程序最后的退出码必须是 1。退出码可以理解成程序离开时留给操作系统的一张小纸条:0 表示成功,非 0 表示失败。这个文件确保当远端已经明确说失败时,命令行工具不会“装作成功”,否则自动化工具就无法可靠发现问题。

函数细节1
exits_non_zero_when_server_reports_error10–33 ↗
async fn exits_non_zero_when_server_reports_error() -> anyhow::Result<()>

作用:这个测试函数验证:当假服务器返回失败事件时,codex-exec 必须以退出码 1 结束。它的作用是防止命令行工具在服务器出错时还错误地报告成功。

数据流:进去时没有外部参数,测试自己创建一个 codex-exec 测试命令和一个假服务器。它把一个包含 response.failed 的 SSE 响应装到假服务器上,再运行命令并传入测试参数。最后它检查命令的退出码,结果必须是 1;如果不是,测试就失败。

调用关系:这个函数是测试框架在运行测试时直接调用的入口。它先用 test_codex_exec 准备被测命令,再用 start_mock_server 启动假服务器,用 sse 生成服务器推送内容,并用 mount_sse_once 安排服务器只返回这一次失败响应;最后它把命令跑起来并断言退出码。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex_exec);外部调用 1 个(vec!)。

core/tests/suite/cli_stream.rs源码 ↗
testtest execution

这个测试文件像一套“假后台 + 真命令行”的验收流程。它会启动一个本地模拟服务器,假装自己是 OpenAI 或 ChatGPT 后端,然后真的运行编译出来的 codex 命令。测试会检查几件关键事:流式响应(服务器一段段吐出回答)能不能显示到终端;个人访问令牌(一种不用网页登录、直接放在环境变量里的登录凭证)会不会被正确带到请求里;遇到 401 未授权时会不会错误地去刷新 OAuth;命令行参数和配置文件里的模型指令文件是否真的进入了请求;会话日志是否按日期写成 jsonl 文件,并且 resume(继续上次会话)会追加到同一个文件。文件里还有专门的子进程清理保护,避免测试超时后留下没关掉的命令行或它启动的子进程。

函数细节15
repo_root34–36 ↗
fn repo_root() -> std::path::PathBuf

作用:找到这个代码仓库的根目录,方便测试在一个真实项目目录里运行 codex exec。如果没有这个位置,命令行测试就不知道该在哪个目录下工作。

数据流:输入上不需要外部参数 → 它调用已有工具去定位仓库根目录 → 返回一个路径;如果找不到,就直接让测试失败,因为后面的命令没法可靠运行。

调用关系:多个测试在组装 codex exec 命令时都会先找仓库根目录。personal_access_token_exec_command 也会用它,把工作目录固定到项目根部,避免测试受当前运行目录影响。

调用图:被 7 处调用(exec_cli_applies_model_instructions_file, exec_cli_profile_applies_model_instructions_file, integration_creates_and_checks_session_file, personal_access_token_exec_command, responses_api_stream_cli, responses_mode_stream_cli, responses_mode_stream_cli_supports_openai_base_url_config_override);外部调用 1 个(repo_root)。

cli_sse_response38–44 ↗
fn cli_sse_response() -> String

作用:造出一段假的流式 AI 回复,内容里包含一条助手消息 fixture hello。测试用它来模拟服务器一边生成一边返回结果。

数据流:输入上不接收参数 → 它把“响应创建、助手消息、响应完成”这几个事件拼成 SSE 字符串;SSE 是 Server-Sent Events,一种服务器连续推送文本事件的格式 → 输出这段可被 CLI 当成真实流式响应读取的字符串。

调用关系:它被个人访问令牌测试和 Responses API 流式测试复用。测试先把这段回复挂到模拟服务器上,然后运行 CLI,看 CLI 是否能正确读出并处理。

调用图:调用 1 个内部函数(sse);被 2 处调用(responses_api_stream_cli, responses_mode_stream_cli_supports_personal_access_tokens);外部调用 1 个(vec!)。

mount_personal_access_token_startup46–67 ↗
async fn mount_personal_access_token_startup(server: &MockServer)

作用:给模拟服务器装上两条启动时会用到的接口:一个确认令牌是谁,另一个返回云端配置。这样测试个人访问令牌时,不需要真的访问线上服务。

数据流:输入是一个模拟服务器 → 它注册两个 GET 接口,并要求请求里必须带指定的 Authorization 头;第一个接口返回用户、账号、企业计划和 FedRAMP 标记,第二个返回空配置 → 服务器之后就能按这些假数据回应 CLI。

调用关系:个人访问令牌相关的两个测试都会先调用它。它负责铺好“登录启动阶段”的假后台,随后 personal_access_token_exec_command 生成命令,run_cli_command 真正运行 CLI。

调用图:被 2 处调用(responses_mode_stream_cli_does_not_attempt_oauth_refresh_for_personal_access_tokens_after_401, responses_mode_stream_cli_supports_personal_access_tokens);外部调用 6 个(given, new, json!, header, method, path)。

personal_access_token_exec_command70–88 ↗
fn personal_access_token_exec_command(server: &MockServer, home: &TempDir) -> Command

作用:准备一条专门用于个人访问令牌测试的 codex exec 命令。它把服务地址、临时主目录、访问令牌等都塞好,避免每个测试重复写一大堆命令参数。

数据流:输入是模拟服务器和临时 HOME 目录 → 它找到 codex 可执行文件,拼出 exec 参数,把 OpenAI 和 ChatGPT 的基础地址都改到模拟服务器,并设置 CODEX_HOME 和个人访问令牌环境变量,同时移除普通 API key → 输出一个还没启动的 Command,供测试运行。

调用关系:它内部会调用 repo_root 来设置工作目录。个人访问令牌成功测试和 401 测试都会用它生成命令,然后交给 run_cli_command 启动并等待结果。

调用图:调用 1 个内部函数(repo_root);被 2 处调用(responses_mode_stream_cli_does_not_attempt_oauth_refresh_for_personal_access_tokens_after_401, responses_mode_stream_cli_supports_personal_access_tokens);外部调用 5 个(uri, path, new, cargo_bin, format!)。

ChildProcessCleanupGuard::drop93–113 ↗
fn drop(&mut self)

作用:在测试结束或出错时清理 CLI 子进程,尤其是它可能带出来的孙进程。它相当于测试里的“保洁员”,防止超时后留下后台进程污染后续测试。

数据流:输入是结构体里保存的进程 ID → 当这个保护对象被释放时,在 Unix 上杀掉整个进程组,在 Windows 上调用 taskkill 连同子进程一起杀掉;其他系统则什么也不做 → 输出不返回值,但会尽力清理系统里的进程。

调用关系run_cli_command 启动 CLI 后立刻创建这个保护对象。只要等待过程结束、超时或测试提前退出,保护对象都会触发清理逻辑。

调用图:调用 1 个内部函数(kill_process_group);外部调用 2 个(null, new)。

run_cli_command119–144 ↗
fn run_cli_command(command: &mut Command) -> io::Result<Output>

作用:真正运行一条 CLI 命令,并给它加上超时和完整的进程清理。它解决的是测试最怕的问题:命令卡住不退出,导致整套测试挂死。

数据流:输入是一条已经配置好的 Command → 它关闭标准输入,收集标准输出和错误输出;在 Unix 上把进程放进新的进程组;启动子进程后开一个线程等待结果,并最多等 30 秒 → 成功时返回命令的退出状态和输出,超时时返回超时错误,同时清理进程组。

调用关系:几乎所有真正跑 codex exec 的测试都通过它执行命令。它把底层启动、等待、超时、清理这些麻烦事封装起来,让各个测试只关心 CLI 的行为是否正确。

调用图:被 8 处调用(exec_cli_applies_model_instructions_file, exec_cli_profile_applies_model_instructions_file, integration_creates_and_checks_session_file, responses_api_stream_cli, responses_mode_stream_cli, responses_mode_stream_cli_does_not_attempt_oauth_refresh_for_personal_access_tokens_after_401, responses_mode_stream_cli_supports_openai_base_url_config_override, responses_mode_stream_cli_supports_personal_access_tokens);外部调用 9 个(null, piped, process_group, spawn, stdin, new, other, sync_channel, spawn)。

responses_mode_stream_cli_supports_personal_access_tokens147–175 ↗
async fn responses_mode_stream_cli_supports_personal_access_tokens()

作用:确认 CLI 用个人访问令牌时,能成功调用 Responses 接口,并且请求里带上正确的认证、账号和 FedRAMP 标记。

数据流:输入来自测试内部搭好的模拟服务器、临时 HOME 和固定访问令牌 → 它挂载启动接口和一段流式回复,运行 codex exec → 输出是断言结果:命令必须成功,请求路径必须是 /api/codex/responses,请求头必须包含预期的令牌、账号 ID 和 FedRAMP 标记。

调用关系:它先让 mount_personal_access_token_startup 准备登录相关接口,再用 cli_sse_response 和响应辅助工具准备模型回复,最后用 personal_access_token_exec_commandrun_cli_command 启动真实 CLI。

调用图:调用 5 个内部函数(mount_sse_once, cli_sse_response, mount_personal_access_token_startup, personal_access_token_exec_command, run_cli_command);外部调用 5 个(start, new, assert!, assert_eq!, skip_if_no_network!)。

responses_mode_stream_cli_does_not_attempt_oauth_refresh_for_personal_access_tokens_after_401178–209 ↗
async fn responses_mode_stream_cli_does_not_attempt_oauth_refresh_for_personal_access_tokens_after_401()

作用:确认个人访问令牌请求遇到 401 未授权时,CLI 不会错误地去走 OAuth 刷新流程。OAuth 是网页登录常用的授权机制,但个人访问令牌不应该用这套刷新方式。

数据流:输入是模拟服务器和个人访问令牌命令 → 它让 Responses 接口固定返回 401,并额外设置 /oauth/token 接口期望被调用 0 次 → 运行 CLI 后,期望命令失败,但服务器验证必须证明没有发生 OAuth 刷新请求。

调用关系:它复用 mount_personal_access_token_startuppersonal_access_token_exec_command 准备环境,再交给 run_cli_command 执行。这个测试专门保护认证分支,防止以后改登录逻辑时把个人令牌误当成 OAuth 登录。

调用图:调用 3 个内部函数(mount_personal_access_token_startup, personal_access_token_exec_command, run_cli_command);外部调用 9 个(given, start, new, new, assert!, skip_if_no_network!, header, method, path)。

responses_mode_stream_cli213–255 ↗
async fn responses_mode_stream_cli()

作用:测试 CLI 在 Responses 模式下能读取流式返回,并把助手回答 hi 正确打印出来。它验证的是普通 API key 场景下最基本的流式对话链路。

数据流:输入是模拟服务器上的 SSE 回复、临时 HOME、仓库目录和一个假 API key → 它通过配置把模型提供方指向模拟服务器,运行 codex exec hello? → 输出是断言:命令成功,标准输出里恰好有一行 hi,并且请求打到了 /v1/responses

调用关系:它自己构造 SSE 响应并挂到服务器上,调用 repo_root 定位工作目录,再用 run_cli_command 执行 CLI。这个测试覆盖的是从命令行到 Responses API 再到终端输出的主路径。

调用图:调用 4 个内部函数(mount_sse_once, sse, repo_root, run_cli_command);外部调用 11 个(start, from_utf8_lossy, new, assert!, assert_eq!, new, cargo_bin, format!, println!, skip_if_no_network! (+1 more))。

responses_mode_stream_cli_supports_openai_base_url_config_override259–289 ↗
async fn responses_mode_stream_cli_supports_openai_base_url_config_override()

作用:确认 openai_base_url 这个配置覆盖项真的会改变内置 OpenAI 提供方的请求地址。这样用户才能把请求导向代理、测试服务器或私有网关。

数据流:输入是模拟服务器地址、临时 HOME、仓库目录和假 API key → 它把 openai_base_url 配成模拟服务器的 /v1,运行 codex exec → 输出是断言:命令成功,并且模拟服务器收到的请求路径是 /v1/responses

调用关系:它用响应辅助工具挂载流式回复,用 repo_root 设置工作目录,用 run_cli_command 跑命令。它和普通流式测试很像,但重点只看基础 URL 覆盖是否生效。

调用图:调用 4 个内部函数(mount_sse_once, sse, repo_root, run_cli_command);外部调用 9 个(start, new, assert!, assert_eq!, new, cargo_bin, format!, skip_if_no_network!, vec!)。

exec_cli_applies_model_instructions_file295–359 ↗
async fn exec_cli_applies_model_instructions_file()

作用:确认命令行参数 -c model_instructions_file=... 指定的模型指令文件会进入发给模型的请求。模型指令可以理解成“给 AI 的底层工作说明”。

数据流:输入是一个临时写出的指令文件,里面有独一无二的标记文本 → 它把模型提供方指向模拟服务器,并通过 -c 指定这个指令文件,运行 codex exec → 输出是检查模拟服务器收到的 JSON 请求体,确认 instructions 字段里包含那个标记。

调用关系:它通过 repo_root 定位项目,用 run_cli_command 执行 CLI,并用响应 mock 捕获请求体。这个测试保护命令行配置覆盖到内部 app-server 线程再到网络请求的完整传递链。

调用图:调用 3 个内部函数(mount_sse_once, repo_root, run_cli_command);外部调用 10 个(start, new, assert!, new, cargo_bin, concat!, format!, println!, skip_if_no_network!, write)。

exec_cli_profile_applies_model_instructions_file365–427 ↗
async fn exec_cli_profile_applies_model_instructions_file()

作用:确认使用 codex exec --profile ... 时,所选配置档里的模型指令文件不会丢失。配置档可以理解成一套命名的用户设置。

数据流:输入是临时 HOME 里的 default.config.toml,其中写着模型指令文件路径;指令文件里有唯一标记 → 它用 --profile default 启动 CLI,并把模型请求导向模拟服务器 → 输出是检查请求体的 instructions 字段,确认包含配置档里的标记。

调用关系:它和 exec_cli_applies_model_instructions_file 类似,但配置来源从命令行 -c 换成 profile 文件。它调用 repo_rootrun_cli_command,用模拟服务器捕获最终请求,验证 profile 在内部启动流程中被保留下来。

调用图:调用 3 个内部函数(mount_sse_once, repo_root, run_cli_command);外部调用 10 个(start, new, assert!, new, cargo_bin, concat!, format!, println!, skip_if_no_network!, write)。

responses_api_stream_cli431–458 ↗
async fn responses_api_stream_cli()

作用:用本地模拟 Responses API 测试 CLI 的流式输出,确认完整回答 fixture hello 能出现在标准输出里。

数据流:输入是模拟服务器上的固定 SSE 回复、临时 HOME、仓库根目录和假 API key → 它把 openai_base_url 指向模拟服务器,运行 codex exec hello? → 输出是断言:命令成功,终端输出包含 fixture hello,请求路径是 /v1/responses

调用关系:它复用 cli_sse_response 生成标准假回复,调用 repo_root 准备工作目录,再交给 run_cli_command 执行。它是一个更直接的 Responses API 流式集成检查。

调用图:调用 4 个内部函数(mount_sse_once, cli_sse_response, repo_root, run_cli_command);外部调用 9 个(start, from_utf8_lossy, new, assert!, assert_eq!, new, cargo_bin, format!, skip_if_no_network!)。

integration_creates_and_checks_session_file462–643 ↗
async fn integration_creates_and_checks_session_file() -> anyhow::Result<()>

作用:端到端验证 CLI 会创建会话日志文件,并且 resume --last 会继续写入同一个会话。会话日志就像聊天记录本,后续恢复上下文要靠它。

数据流:输入是临时 HOME、两个带唯一标记的提示词、以及模拟服务器连续返回的两段流式响应 → 它先运行一次 codex exec,等待 sessions 目录和 jsonl 文件出现,检查文件路径按 年/月/日 分层,第一行有会话元数据,后续内容包含第一个标记;然后再运行一次带 resume --last 的命令 → 输出是断言:第二个标记出现在同一个文件里,原标记也还在,说明是追加而不是新建。

调用关系:它调用 repo_root 设置工作目录,用 run_cli_command 跑两次真实 CLI,用测试辅助函数等待文件落盘,用响应序列 mock 保证两次请求都有假回复。这个测试覆盖网络、命令行、会话写盘和恢复会话的组合行为。

调用图:调用 3 个内部函数(mount_sse_sequence, repo_root, run_cli_command);外部调用 14 个(from_secs, start, new, assert!, assert_eq!, new, cargo_bin, format!, wait_for_matching_file, wait_for_path_exists (+4 more))。

integration_git_info_unit_test647–787 ↗
async fn integration_git_info_unit_test()

作用:验证 Git 信息收集能在一个真实临时仓库里拿到提交哈希、分支名和远程仓库地址,并且这些信息能被序列化进 JSON。Git 信息会被写进会话元数据,帮助以后知道一次会话发生在哪个代码版本上。

数据流:输入是一个临时目录 → 它在里面初始化 Git 仓库,配置用户名邮箱,提交一个文件,创建分支,添加远程地址;然后调用 collect_git_info 收集信息 → 输出是断言:提交哈希存在且像 40 位十六进制,分支名正确,远程地址和 git remote get-url 一致,并且序列化再反序列化后内容不变。

调用关系:这个测试不跑完整 CLI,而是直接验证 Git 信息收集函数。它补上了会话元数据里 Git 部分的可信度,避免端到端测试太重或受其他 CLI 环节影响。

调用图:外部调用 11 个(from_utf8, new, assert!, assert_eq!, new, collect_git_info, println!, from_str, to_string, write (+1 more))。