Codex 系统手册

会话历史、压缩、恢复与持久状态套件

stage-23.2.4.213 个文件

这一阶段像给聊天系统做“记忆体检”,属于幕后支撑,但会影响重启后能不能接着聊。它检查长对话变短摘要时不丢重点,旧会话恢复、分叉、回滚时历史不乱;用户半路追加输入时能排队或打断得当。还会验 rollout 记录和 SQLite 状态库,确保文字、图片、工具、线程设置都保存对;临时换模型只影响本次,不改长期配置。请求头、模型变化提醒、旧记录查找这些细节,也一起防止系统把不同对话串线。

本阶段的文件13

压缩行为

这些套件确立核心压缩语义,比较远程实现,并跟踪压缩后的历史如何经过恢复和分叉流程。

core/tests/suite/compact.rs源码 ↗
testtest suite

大模型一次能看的内容有上限,叫上下文窗口。对话太长时,系统需要把旧聊天“压缩”成摘要,好比把一大摞会议记录整理成一页纪要,再带着纪要继续开会。这个测试文件专门盯住这件事:手动 /compact 是否会发总结请求;自动压缩是否在 token(可粗略理解为模型读写文字的计数单位)超限后触发;压缩后新请求里是否保留用户消息、环境信息和全局说明;失败、重试、恢复旧会话、切换到更小模型时是否安全。它还会检查事件流、rollout 持久化文件、hooks(用户配置的外部脚本)和远程压缩接口。没有这些测试,压缩功能很容易悄悄丢历史、用错模型、重复触发,或者恢复会话后带入错误说明。

函数细节64
ev_shell_command_call94–100 ↗
fn ev_shell_command_call(call_id: &str, command: &str) -> serde_json::Value

作用:造一个假的“调用 shell 命令”模型事件,供测试里模拟模型要求执行命令。这样测试不用真的等模型生成工具调用。

数据流:输入调用编号和命令文本 → 把命令包成 JSON 参数 → 输出一个标准的 function_call 事件值。

调用关系:它把具体造事件的活交给 ev_function_call,主要被自动压缩相关测试用来制造“模型中途调用工具”的历史片段。

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

disabled_permission_user_turn102–129 ↗
fn disabled_permission_user_turn(text: impl Into<String>, cwd: PathBuf, model: String) -> Op

作用:生成一个权限关闭、永不请求批准的用户输入操作。测试模型切换或恢复会话时,用它保证权限设置固定,不受外部环境影响。

数据流:输入用户文字、当前目录和模型名 → 生成沙箱和权限字段,并把目录、审批策略、模型选择塞进线程设置 → 输出一个 Op::UserInput 操作。

调用关系:多个预采样压缩和恢复会话测试会调用它,避免每个测试重复写一大段用户输入配置。

调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 9 处调用(auto_compact_runs_after_resume_when_token_usage_is_over_limit, body_after_prefix_model_switch_budget_compacts_with_next_model, pre_sampling_compact_recovers_comp_hash_after_resume, pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model, pre_sampling_compact_runs_on_switch_to_smaller_context_model, pre_sampling_compact_runs_when_comp_hash_changes, pre_sampling_compact_skips_missing_comp_hash_after_resume, pre_sampling_compact_skips_when_either_comp_hash_is_missing, snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch);外部调用 4 个(default, abs, as_path, vec!)。

auto_summary131–133 ↗
fn auto_summary(summary: &str) -> String

作用:把摘要文本原样转成 String。它让测试代码读起来像是在准备“自动压缩返回的摘要”。

数据流:输入一段摘要字符串 → 复制成拥有所有权的字符串 → 输出该字符串,不改任何外部状态。

调用关系:被手动压缩两次、自动压缩持久化、多次自动压缩等测试调用,用来准备假模型返回内容。

调用图:被 5 处调用(auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_events, auto_compact_clamps_config_limit_to_context_window, auto_compact_persists_rollout_entries, manual_compact_twice_preserves_latest_user_messages, snapshot_request_shape_mid_turn_continuation_compaction)。

summary_with_prefix135–137 ↗
fn summary_with_prefix(summary: &str) -> String

作用:给摘要加上系统规定的摘要前缀。测试用它确认压缩后的历史不是裸摘要,而是带有可识别标记的摘要消息。

数据流:输入摘要文本 → 在前面拼上 SUMMARY_PREFIX 和换行 → 输出拼好的摘要消息。

调用关系:被检查请求形状的测试调用,用来和真正发给模型的压缩历史做对比。

调用图:被 3 处调用(manual_compact_twice_preserves_latest_user_messages, multiple_auto_compact_per_task_runs_after_token_limit_hit, summarize_context_three_requests_and_instructions);外部调用 1 个(format!)。

set_test_compact_prompt139–141 ↗
fn set_test_compact_prompt(config: &mut Config)

作用:把配置里的压缩提示词固定成测试用的默认提示词。这样测试能明确知道压缩请求里应该出现什么文字。

数据流:输入可修改的 Config → 设置 compact_prompt 字段 → 不返回新值,但配置被改好。

调用关系:大量测试在创建 Codex 前调用它,保证压缩提示词一致,方便后面检查请求内容。

ev_completed_with_usage143–157 ↗
fn ev_completed_with_usage(id: &str, input_tokens: i64, output_tokens: i64) -> Value

作用:造一个带输入 token 和输出 token 统计的完成事件。它用来测试系统按不同 token 口径触发压缩。

数据流:输入响应 id、输入 token 数、输出 token 数 → 组装 response.completed JSON → 输出该事件值。

调用关系:由一些测试直接使用,和 sse 事件流配合,模拟模型 API 返回详细用量。

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

body_contains_text159–161 ↗
fn body_contains_text(body: &str, text: &str) -> bool

作用:检查一段请求正文里是否包含某个文本的 JSON 转义形式。这样即使文本里有特殊字符,也能按请求真实格式去找。

数据流:输入请求正文字符串和要找的文字 → 先把文字转成 JSON 片段 → 返回正文是否包含它。

调用关系:很多断言用它确认压缩提示词是否出现在请求中;它内部依赖 json_fragment 做转义。

调用图:调用 1 个内部函数(json_fragment);被 2 处调用(manual_compact_retries_after_context_window_error, summarize_context_three_requests_and_instructions)。

json_fragment163–168 ↗
fn json_fragment(text: &str) -> String

作用:把普通文本变成 JSON 字符串内部会出现的样子。比如引号、换行会按 JSON 规则转义。

数据流:输入普通文本 → 用 serde_json 序列化,再去掉最外层引号 → 输出可用于 contains 检查的片段。

调用关系:只被 body_contains_text 调用,是请求正文文本匹配的小工具。

调用图:被 1 处调用(body_contains_text);外部调用 1 个(to_string)。

read_hook_inputs170–176 ↗
fn read_hook_inputs(path: &Path) -> Vec<Value>

作用:读取 hook 脚本写下来的输入日志。测试用它确认系统有没有把正确的压缩事件信息交给外部脚本。

数据流:输入日志文件路径 → 读取每一行 JSON → 输出 JSON 值列表。

调用关系:被 hook 相关测试调用,用来检查 PreCompact 和 PostCompact 收到的字段。

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

python_hook_command178–180 ↗
fn python_hook_command(script_path: &Path) -> String

作用:生成执行某个 Python 脚本的命令字符串。测试配置 hook 时需要把脚本路径写成命令。

数据流:输入脚本路径 → 拼成 python3 "路径" → 输出命令文本。

调用关系:写 hook 配置的辅助函数会调用它,把临时脚本挂到测试用 hooks.json 里。

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

write_unsupported_blocking_pre_compact_hook182–213 ↗
fn write_unsupported_blocking_pre_compact_hook(home: &Path)

作用:在临时 home 目录里写一个会尝试“阻止压缩”的 PreCompact hook。测试用它确认这种不支持的阻止结果不会真的挡住压缩。

数据流:输入 home 路径 → 写 Python 脚本、日志路径和 hooks.json → 文件系统里多出 hook 配置和脚本。

调用关系:被手动压缩 hook 阻止测试作为预构建钩子使用,随后测试会读日志验证它确实运行过。

调用图:外部调用 4 个(join, format!, write, json!)。

write_matching_compact_hooks215–265 ↗
fn write_matching_compact_hooks(home: &Path)

作用:写一组带匹配条件的压缩前后 hook。它用来测试手动压缩只触发 manual 匹配的 PostCompact,不触发 auto 匹配的 PreCompact。

数据流:输入 home 路径 → 写两个 Python 脚本和 hooks.json → 产生可被 Codex 发现的测试 hook 配置。

调用关系:被 compact_hooks_respect_matchers_and_post_runs_after_compaction 调用,配合 read_hook_inputs 检查运行结果。

调用图:外部调用 4 个(join, format!, write, json!)。

non_openai_model_provider267–274 ↗
fn non_openai_model_provider(server: &MockServer) -> ModelProviderInfo

作用:造一个指向本地假服务器的 OpenAI 兼容模型提供方。测试不用打真实网络,也能像调用模型一样检查请求。

数据流:输入 MockServer → 复制内置 openai provider,改名、改 base_url、关掉 websocket → 输出测试用 provider。

调用关系:绝大多数本文件测试都用它把 Codex 请求导向 mock server。

调用图:被 31 处调用(auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_events, auto_compact_body_after_prefix_counts_growth_after_compaction, auto_compact_body_after_prefix_ignores_starting_window_prefix, auto_compact_body_after_prefix_still_caps_at_context_window, auto_compact_clamps_config_limit_to_context_window, auto_compact_emits_context_compaction_items, auto_compact_persists_rollout_entries, auto_compact_runs_after_token_limit_hit, auto_compact_starts_after_turn_started, body_after_prefix_model_switch_budget_compacts_with_next_model (+15 more));外部调用 2 个(built_in_model_providers, format!)。

write_global_file276–284 ↗
fn write_global_file(
    home: &TempDir,
    filename: &str,
    contents: impl AsRef<[u8]>,
) -> Result<AbsolutePathBuf>

作用:在临时 home 目录写一个全局说明文件,比如 AGENTS.md。测试用它模拟用户的全局指令文件被创建或改写。

数据流:输入临时目录、文件名、内容 → 写入磁盘 → 输出该文件的绝对路径。

调用关系:被全局说明与压缩相关测试调用,用来检查压缩后是否仍使用会话创建时的说明快照。

调用图:被 3 处调用(manual_compaction_keeps_the_creation_time_global_instructions, mid_turn_compaction_keeps_the_creation_time_global_instructions, remote_v2_compaction_keeps_creation_time_instructions_after_same_path_mutation);外部调用 2 个(path, write)。

instruction_fragments286–292 ↗
fn instruction_fragments(request: &responses::ResponsesRequest) -> Vec<String>

作用:从一次模型请求中挑出全局 AGENTS.md 指令片段。测试用它确认请求里带的是旧说明还是新说明。

数据流:输入 ResponsesRequest → 取出 user 角色文本 → 过滤以 AGENTS.md 指令标题开头的文本 → 输出这些片段。

调用关系:assert_single_instruction_fragment 会通过它做精确断言。

调用图:调用 1 个内部函数(message_input_texts)。

instruction_fragments_in_items294–307 ↗
fn instruction_fragments_in_items(items: &[Value]) -> Vec<String>

作用:从一组结构化历史 item 里找 AGENTS.md 指令片段。它主要用于检查远程压缩保存的替换历史里有没有夹带全局说明。

数据流:输入 JSON item 列表 → 只看 user message 的 content 文本 → 过滤 AGENTS.md 指令片段 → 输出字符串列表。

调用关系:远程 v2 压缩恢复测试用它检查 rollout 里的 replacement_history。

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

expected_instruction_fragment309–311 ↗
fn expected_instruction_fragment(contents: &str) -> String

作用:按系统实际格式拼出一段期望的 AGENTS.md 指令文本。这样测试不用手写重复模板。

数据流:输入说明内容 → 包进标题和 <INSTRUCTIONS> 标签 → 输出完整片段。

调用关系:全局说明快照相关测试用它生成断言目标,再交给 assert_single_instruction_fragment。

调用图:被 3 处调用(manual_compaction_keeps_the_creation_time_global_instructions, mid_turn_compaction_keeps_the_creation_time_global_instructions, remote_v2_compaction_keeps_creation_time_instructions_after_same_path_mutation);外部调用 1 个(format!)。

assert_single_instruction_fragment313–315 ↗
fn assert_single_instruction_fragment(request: &responses::ResponsesRequest, expected: &str)

作用:断言某个请求里只有一段全局说明,而且内容正好等于预期。它让相关测试的核心检查更直观。

数据流:输入请求和期望文本 → 提取请求中的说明片段 → 用断言比较为单元素列表。

调用关系:被手动压缩、中途自动压缩和远程 v2 压缩的全局说明测试反复调用。

调用图:被 3 处调用(manual_compaction_keeps_the_creation_time_global_instructions, mid_turn_compaction_keeps_the_creation_time_global_instructions, remote_v2_compaction_keeps_creation_time_instructions_after_same_path_mutation);外部调用 1 个(assert_eq!)。

replacement_history_from_rollout317–338 ↗
fn replacement_history_from_rollout(path: &Path) -> Result<Vec<Value>>

作用:从 rollout 持久化文件里找出压缩后的替换历史。rollout 可以理解为会话日志,恢复会话时会靠它重建状态。

数据流:输入 rollout 路径 → 逐行读取并解析 JSON → 找到 Compacted 条目的 replacement_history → 输出这些历史 item 的 JSON 列表;找不到则报错。

调用关系:远程 v2 压缩恢复测试用它确认冷启动恢复时会重放同一段压缩历史。

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

remote_v2_compaction_response340–351 ↗
fn remote_v2_compaction_response() -> String

作用:造一个远程 v2 压缩接口会返回的 SSE 事件流。SSE 是服务器一条条推事件的格式。

数据流:无输入 → 组装一个 compaction 输出项和完成事件 → 输出 SSE 字符串。

调用关系:远程 v2 全局说明测试用它模拟服务端返回加密摘要。

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

local_compaction_provider353–359 ↗
fn local_compaction_provider(server: &wiremock::MockServer) -> ModelProviderInfo

作用:造一个本地压缩测试用的 OpenAI 兼容 provider。它和 non_openai_model_provider 类似,但名字专门标明是兼容测试提供方。

数据流:输入 mock server → 复制内置 provider 并改 base_url、名称和 websocket 支持标记 → 输出 provider。

调用关系:全局说明在手动压缩和中途压缩中保持不变的测试会调用它。

调用图:被 2 处调用(manual_compaction_keeps_the_creation_time_global_instructions, mid_turn_compaction_keeps_the_creation_time_global_instructions);外部调用 2 个(built_in_model_providers, format!)。

model_info_with_context_window361–370 ↗
fn model_info_with_context_window(slug: &str, context_window: i64) -> ModelInfo

作用:从内置模型列表里取出某个模型信息,并强行设置它的上下文窗口大小。测试用它模拟大模型和小模型。

数据流:输入模型 slug 和窗口大小 → 读取 bundled models.json,找到对应模型 → 修改 context_window → 输出模型信息。

调用关系:模型切换测试和 model_info_with_optional_comp_hash 借它构造远程模型列表。

调用图:被 1 处调用(model_info_with_optional_comp_hash);外部调用 1 个(bundled_models_response)。

model_info_with_optional_comp_hash372–376 ↗
fn model_info_with_optional_comp_hash(slug: &str, comp_hash: Option<&str>) -> ModelInfo

作用:构造带或不带 comp_hash 的模型信息。comp_hash 可以理解为压缩兼容性的版本标记,变了就可能需要先压缩。

数据流:输入模型 slug 和可选 hash → 先生成带上下文窗口的模型信息 → 设置 comp_hash → 输出模型信息。

调用关系:comp_hash 变化或缺失的预采样压缩测试用它准备模型列表。

调用图:调用 1 个内部函数(model_info_with_context_window)。

assert_pre_sampling_switch_compaction_requests378–403 ↗
fn assert_pre_sampling_switch_compaction_requests(
    first: &serde_json::Value,
    compact: &serde_json::Value,
    follow_up: &serde_json::Value,
    previous_model: &str,
    next_model: &str,
)

作用:统一检查“切换模型前先压缩”的三次请求是否长得对。它确认压缩用旧模型,后续对话用新模型,并且模型切换标记只出现在后续请求。

数据流:输入初始、压缩、跟进三个请求以及旧模型名、新模型名 → 检查 model 字段、压缩提示词和 <model_switch> 位置 → 不返回值,失败就让测试失败。

调用关系:多个模型切换和恢复测试调用它,避免重复写同样断言。

调用图:被 4 处调用(pre_sampling_compact_recovers_comp_hash_after_resume, pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model, pre_sampling_compact_runs_on_switch_to_smaller_context_model, pre_sampling_compact_runs_when_comp_hash_changes);外部调用 3 个(to_string, assert!, assert_eq!)。

assert_compaction_uses_turn_lifecycle_id405–445 ↗
async fn assert_compaction_uses_turn_lifecycle_id(codex: &std::sync::Arc<codex_core::CodexThread>)

作用:确认压缩事件和当前用户回合使用同一个生命周期 id。这样前端或日志能把“这次压缩”归到正确的一轮对话里。

数据流:输入 CodexThread → 持续读取事件,记录 TurnStarted、压缩开始、压缩完成、TurnComplete 的 id → 断言这些关键 id 一致。

调用关系:预采样压缩和 body-after-prefix 模型切换测试会调用它,检查事件顺序和归属。

调用图:被 5 处调用(body_after_prefix_model_switch_budget_compacts_with_next_model, pre_sampling_compact_recovers_comp_hash_after_resume, pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model, pre_sampling_compact_runs_on_switch_to_smaller_context_model, pre_sampling_compact_runs_when_comp_hash_changes);外部调用 1 个(assert_eq!)。

context_snapshot_options446–450 ↗
fn context_snapshot_options() -> ContextSnapshotOptions

作用:设置请求快照的渲染方式,让快照只保留测试关心的上下文形状。快照就是把请求样子存下来,防止以后无意改坏。

数据流:无输入 → 从默认选项开始,去掉能力说明并限制文本长度 → 输出快照选项。

调用关系:format_labeled_requests_snapshot 会调用它来生成稳定、易读的 insta 快照。

调用图:调用 1 个内部函数(default);被 1 处调用(format_labeled_requests_snapshot)。

format_labeled_requests_snapshot452–461 ↗
fn format_labeled_requests_snapshot(
    scenario: &str,
    sections: &[(&str, &core_test_support::responses::ResponsesRequest)],
) -> String

作用:把多个带标签的请求整理成一段快照文本。测试失败时,人能更容易看出压缩前后请求差异。

数据流:输入场景说明和若干请求 → 套用 context_snapshot_options → 输出格式化后的快照字符串。

调用关系:多个 snapshot_request_shape 和手动压缩历史测试调用它生成 insta 快照。

调用图:调用 2 个内部函数(format_labeled_requests_snapshot, context_snapshot_options)。

summarize_context_three_requests_and_instructions464–668 ↗
async fn summarize_context_three_requests_and_instructions()

作用:测试一次普通对话、一次手动压缩、再一次对话的完整流程。重点看压缩请求带提示词,压缩后历史只留下用户消息和摘要,不留下旧助手回复。

数据流:搭好三段假模型响应 → 依次提交用户输入、Compact、后续用户输入 → 读取请求和 rollout 文件 → 断言请求数量、提示词、摘要和持久化条目都正确。

调用关系:它调用 mock server、non_openai_model_provider、body_contains_text、summary_with_prefix 等辅助函数,是手动压缩基础行为的主测试之一。

调用图:调用 7 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, body_contains_text, non_openai_model_provider, summary_with_prefix);外部调用 11 个(default, new, assert!, assert_eq!, wait_for_event, panic!, println!, from_str, skip_if_no_network!, read_to_string (+1 more))。

manual_pre_compact_block_decision_does_not_block_compaction671–741 ↗
async fn manual_pre_compact_block_decision_does_not_block_compaction()

作用:测试手动压缩前的 hook 即使输出“block”,也不会阻止压缩。因为当前这种阻止结果对 PreCompact 不支持,只应记为失败。

数据流:写入会 block 的 hook → 提交一轮对话和手动压缩 → 等 hook 完成、警告和回合完成 → 检查仍然发了压缩请求,并检查 hook 输入日志。

调用关系:它使用 write_unsupported_blocking_pre_compact_hook 配置环境,并用 read_hook_inputs 验证脚本收到的内容。

调用图:调用 6 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider, read_hook_inputs);外部调用 7 个(default, assert!, assert_eq!, wait_for_event, wait_for_event_match, skip_if_no_network!, vec!)。

compact_hooks_respect_matchers_and_post_runs_after_compaction744–809 ↗
async fn compact_hooks_respect_matchers_and_post_runs_after_compaction()

作用:测试压缩 hook 的匹配条件是否生效,以及 PostCompact 是否在压缩后运行。手动压缩不应该触发 auto 匹配的 hook。

数据流:写入 auto PreCompact 和 manual PostCompact hook → 运行一次手动压缩 → 检查请求数、auto 日志不存在、manual post 日志存在且字段正确。

调用关系:它调用 write_matching_compact_hooks 间接布置 hook,再用 read_hook_inputs 读取结果。

调用图:调用 6 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider, read_hook_inputs);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

manual_compact_uses_custom_prompt812–903 ↗
async fn manual_compact_uses_custom_prompt()

作用:测试用户自定义压缩提示词时,手动压缩会使用它,而不是默认提示词。这样用户可以控制摘要风格。

数据流:配置 custom_prompt → 先对话再手动压缩 → 查看第二个请求的 input → 断言自定义提示词出现且默认提示词不出现。

调用关系:它依赖 non_openai_model_provider 和 mock SSE 响应,专门覆盖 compact_prompt 配置。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 7 个(default, assert!, assert_eq!, wait_for_event, panic!, skip_if_no_network!, vec!)。

manual_compact_emits_api_and_local_token_usage_events906–961 ↗
async fn manual_compact_emits_api_and_local_token_usage_events()

作用:测试手动压缩会发出两类 token 统计事件:API 返回的用量,以及压缩后本地估算的上下文大小。

数据流:模拟压缩 API 报告 0 token → 触发 Compact → 连续等两个 TokenCount 事件 → 断言第一个是 0,第二个本地估算大于 0。

调用关系:它使用单次 SSE mock,检查压缩流程里事件通知是否完整。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 6 个(assert!, assert_eq!, wait_for_event, wait_for_event_match, skip_if_no_network!, vec!)。

manual_compact_emits_context_compaction_items964–1038 ↗
async fn manual_compact_emits_context_compaction_items()

作用:测试手动压缩会发出新的 ContextCompaction 开始/完成 item,同时仍保留旧的 ContextCompacted 事件。这样新旧客户端都能感知压缩。

数据流:先提交普通用户输入,再提交 Compact → 持续读事件 → 收集压缩 item 开始、完成和旧事件 → 断言开始完成 id 一致且旧事件存在。

调用关系:它覆盖事件层行为,配合 mock SSE 的普通回复和摘要回复。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

multiple_auto_compact_per_task_runs_after_token_limit_hit1041–1583 ↗
async fn multiple_auto_compact_per_task_runs_after_token_limit_hit()

作用:测试同一个任务里多次触发自动压缩时,历史会一步步被新摘要替换,而不是越堆越乱。它模拟模型连续做 react、node、python 三段工作。

数据流:准备七段模型响应:三段工作、三次摘要、一次最终回复 → 提交一次用户任务 → 收集所有请求 → 规范化输入并逐项比较每次压缩前后的历史形状。

调用关系:它使用 ev_shell_command_call、ev_reasoning_item、summary_with_prefix 等辅助函数,是自动压缩历史重写的重量级测试。

调用图:调用 7 个内部函数(ev_reasoning_item, mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider, summary_with_prefix);外部调用 6 个(default, assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!)。

auto_compact_runs_after_token_limit_hit1588–1785 ↗
async fn auto_compact_runs_after_token_limit_hit()

作用:测试当 token 用量超过配置上限后,下一次用户输入前会自动压缩一次,然后再发送真正的后续请求。

数据流:配置自动压缩上限 → 提交三次用户输入 → 查看四次模型请求 → 找出包含压缩提示词的请求,确认它在第三次请求位置,后续请求包含新用户消息和摘要。

调用关系:它用 non_openai_model_provider 指向假服务器,是自动压缩触发时机的基础测试。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

auto_compact_emits_context_compaction_items1790–1871 ↗
async fn auto_compact_emits_context_compaction_items()

作用:测试自动压缩也会发出 ContextCompaction 开始/完成 item 和旧的 ContextCompacted 事件。自动触发不能只在后台悄悄发生。

数据流:连续提交几个用户输入,期间 token 超限触发自动压缩 → 读取事件直到普通回合完成 → 记录压缩 item 和旧事件 → 断言它们存在且 id 对得上。

调用关系:它和手动压缩事件测试互补,覆盖自动路径。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 5 个(default, assert!, assert_eq!, skip_if_no_network!, vec!)。

auto_compact_starts_after_turn_started1876–1975 ↗
async fn auto_compact_starts_after_turn_started()

作用:测试自动压缩的开始事件不会早于当前用户回合开始事件。事件顺序错了,界面可能会先显示压缩再显示用户回合,造成混乱。

数据流:制造一次会触发自动压缩的后续输入 → 读取第一个相关事件 → 断言先看到 TurnStarted,再看到 ContextCompaction 开始,最后回合完成。

调用关系:它依赖 wait_for_event_match 观察事件顺序,覆盖自动压缩和回合生命周期的衔接。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 6 个(default, assert_eq!, wait_for_event, wait_for_event_match, skip_if_no_network!, vec!)。

auto_compact_runs_after_resume_when_token_usage_is_over_limit1978–2086 ↗
async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit()

作用:测试会话恢复后,如果之前已经超过 token 上限,下一次用户输入前仍会自动压缩。也就是说超限状态不能因为重启丢掉。

数据流:先跑一轮超限对话并保存 rollout → 用同一 home 和 rollout 恢复会话 → 提交后续用户输入 → 检查远程 compact 接口被调用一次,后续请求包含远程摘要。

调用关系:它调用 disabled_permission_user_turn 生成固定权限输入,并用 mount_compact_json_once 模拟远程压缩。

调用图:调用 7 个内部函数(mount_compact_json_once, mount_sse_once, mount_sse_once_match, sse, start_mock_server, test_codex, disabled_permission_user_turn);外部调用 6 个(assert!, assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!)。

pre_sampling_compact_runs_on_switch_to_smaller_context_model2089–2188 ↗
async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model()

作用:测试从大上下文模型切到小上下文模型前,会先用旧模型压缩历史。否则旧历史可能塞不进新模型。

数据流:mock 两个模型的上下文窗口 → 先用旧模型对话,再提交切到小模型的输入 → 收集三次请求 → 断言中间出现压缩请求,后续请求使用新模型。

调用关系:它使用 model_info_with_context_window、disabled_permission_user_turn、assert_pre_sampling_switch_compaction_requests 和生命周期 id 检查。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, assert_compaction_uses_turn_lifecycle_id, assert_pre_sampling_switch_compaction_requests, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(start, assert_eq!, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。

pre_sampling_compact_runs_when_comp_hash_changes2191–2275 ↗
async fn pre_sampling_compact_runs_when_comp_hash_changes()

作用:测试即使上下文不大,只要模型的 comp_hash 变了,也会先压缩。comp_hash 代表压缩历史兼容性,变化说明旧压缩格式可能不适合新模型。

数据流:mock 两个 comp_hash 不同的模型 → 先旧模型对话,再切新模型 → 断言请求顺序为普通、压缩、跟进。

调用关系:它复用模型切换断言辅助函数,覆盖触发原因从窗口变小换成 comp_hash 改变。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, assert_compaction_uses_turn_lifecycle_id, assert_pre_sampling_switch_compaction_requests, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

pre_sampling_compact_skips_when_either_comp_hash_is_missing2278–2385 ↗
async fn pre_sampling_compact_skips_when_either_comp_hash_is_missing()

作用:测试只要切换前后任一模型缺少 comp_hash,就不要因为 hash 做预采样压缩。信息不完整时不能乱判断不兼容。

数据流:mock 一个无 hash、一个有 hash、再一个无 hash 的模型 → 连续提交三轮模型切换输入 → 检查只有三次普通请求,没有压缩提示词。

调用关系:它使用 disabled_permission_user_turn 和模型列表 mock,验证“缺 hash 就跳过”的保护逻辑。

调用图:调用 6 个内部函数(mount_models_once, mount_sse_sequence, test_codex, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

body_after_prefix_model_switch_budget_compacts_with_next_model2388–2479 ↗
async fn body_after_prefix_model_switch_budget_compacts_with_next_model()

作用:测试 body-after-prefix 计数模式下,模型切换导致压缩时,压缩请求会使用下一个模型。body-after-prefix 指只按固定前缀之后增长的正文预算来算。

数据流:配置 body-after-prefix 上限和远程模型 → 先旧模型对话,再切新模型 → 等压缩生命周期结束 → 检查三次请求中后两次都用新模型,压缩请求带提示词。

调用关系:它调用 disabled_permission_user_turn 和 assert_compaction_uses_turn_lifecycle_id,覆盖一种特殊预算模式。

调用图:调用 7 个内部函数(mount_models_once, mount_sse_sequence, test_codex, assert_compaction_uses_turn_lifecycle_id, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model2482–2600 ↗
async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model()

作用:测试恢复旧会话后再切到更小模型,仍然会先压缩。恢复过程不能丢掉上一次模型和 token 状态。

数据流:初始会话用大模型对话并关机 → 从 rollout 恢复 → 提交切到小模型的输入 → 检查出现普通、压缩、跟进三次请求。

调用关系:它结合 resume 流程和模型切换辅助断言,覆盖冷恢复后的预采样压缩。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, assert_compaction_uses_turn_lifecycle_id, assert_pre_sampling_switch_compaction_requests, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

pre_sampling_compact_recovers_comp_hash_after_resume2603–2731 ↗
async fn pre_sampling_compact_recovers_comp_hash_after_resume()

作用:测试恢复会话时能从 rollout 找回上一轮的 comp_hash,并据此在 hash 改变时触发压缩。

数据流:旧模型对话后关机 → 读取 rollout 确认保存了 hash-a → 恢复后切到 hash-b 模型 → 断言发生预采样压缩。

调用关系:它调用 assert_pre_sampling_switch_compaction_requests 和 assert_compaction_uses_turn_lifecycle_id,专门覆盖 comp_hash 持久化。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, assert_compaction_uses_turn_lifecycle_id, assert_pre_sampling_switch_compaction_requests, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(start, assert_eq!, wait_for_event, read_to_string, skip_if_no_network!, vec!)。

pre_sampling_compact_skips_missing_comp_hash_after_resume2734–2860 ↗
async fn pre_sampling_compact_skips_missing_comp_hash_after_resume()

作用:测试恢复后如果旧会话没有保存 comp_hash,就不会因为新模型有 hash 而贸然压缩。

数据流:用无 hash 旧模型对话并关机 → 检查 rollout 没有 comp_hash → 恢复后切到有 hash 新模型 → 断言只有两次普通请求,没有压缩提示词。

调用关系:它是前一个恢复 hash 测试的反面案例,确保缺信息时跳过。

调用图:调用 6 个内部函数(mount_models_once, mount_sse_sequence, test_codex, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(start, assert!, assert_eq!, wait_for_event, read_to_string, skip_if_no_network!, vec!)。

auto_compact_persists_rollout_entries2863–3000 ↗
async fn auto_compact_persists_rollout_entries()

作用:测试自动压缩不会破坏 rollout 中每个真实用户回合的 TurnContext 记录。TurnContext 是恢复会话时理解每轮上下文的重要记录。

数据流:配置自动压缩并提交三次用户输入 → 等完成后关机刷盘 → 读取 rollout → 统计 TurnContext 条目数量应为三。

调用关系:它用匹配器挂载四段响应,确保普通请求和压缩请求按预期命中。

调用图:调用 6 个内部函数(mount_sse_once_match, sse, start_mock_server, test_codex, auto_summary, non_openai_model_provider);外部调用 7 个(default, assert_eq!, wait_for_event, from_str, skip_if_no_network!, read_to_string, vec!)。

manual_compact_retries_after_context_window_error3003–3103 ↗
async fn manual_compact_retries_after_context_window_error()

作用:测试手动压缩遇到上下文窗口超限错误时,会丢掉最旧的一项历史再重试。这样还有机会把太长的历史压缩成功。

数据流:先普通对话 → 第一次压缩返回 context_length_exceeded → 第二次压缩成功 → 比较两次压缩请求,确认重试少了一项历史且提示词使用一致。

调用关系:它使用 sse_failed 模拟错误,并用 body_contains_text 检查提示词。

调用图:调用 7 个内部函数(mount_sse_sequence, sse, sse_failed, start_mock_server, test_codex, body_contains_text, non_openai_model_provider);外部调用 7 个(default, assert_eq!, assert_ne!, wait_for_event, panic!, skip_if_no_network!, vec!)。

manual_compact_non_context_failure_retries_then_emits_task_error3109–3182 ↗
async fn manual_compact_non_context_failure_retries_then_emits_task_error()

作用:这个被忽略的测试描述了非上下文错误时的期望行为:重试后仍失败应发任务错误。当前标记 ignore,说明主线行为还没完全符合。

数据流:准备普通响应和两次 server_error 压缩失败 → 触发 Compact → 期望看到重连提示和本地压缩任务错误 → 最后回合完成。

调用关系:它依赖 provider 的 stream_max_retries 设置;虽然不运行,但记录了后续修复要满足的行为。

调用图:调用 6 个内部函数(mount_sse_sequence, sse, sse_failed, start_mock_server, test_codex, non_openai_model_provider);外部调用 6 个(default, assert!, wait_for_event, wait_for_event_match, skip_if_no_network!, vec!)。

manual_compact_twice_preserves_latest_user_messages3185–3433 ↗
async fn manual_compact_twice_preserves_latest_user_messages()

作用:测试连续手动压缩两次后,后续请求仍保留最近的用户消息和最新摘要。重点防止压缩把重要用户输入吞掉。

数据流:提交第一轮、压缩、第二轮、再压缩、最终跟进 → 检查五次请求中的用户文本、metadata、窗口 id 和快照形状 → 断言最终历史顺序正确。

调用关系:它用 auto_summary、summary_with_prefix、format_labeled_requests_snapshot,覆盖手动压缩后的历史和请求元数据。

调用图:调用 7 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, auto_summary, non_openai_model_provider, summary_with_prefix);外部调用 9 个(default, assert!, assert_eq!, assert_ne!, wait_for_event, assert_snapshot!, from_str, skip_if_no_network!, vec!)。

auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_events3436–3547 ↗
async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_events()

作用:测试自动压缩可以在普通工具调用等事件夹杂时多次发生,并且不会额外发出独立任务生命周期事件。

数据流:配置很低的自动压缩上限 → 提交三次用户输入,中间模拟工具调用和大回复 → 收集事件和请求 → 断言有两次压缩请求,但没有 auto-compact 的 TurnStarted/TurnComplete 事件。

调用关系:它使用 ev_function_call 和 auto_summary 构造复杂中途历史,检查自动压缩不会和普通回合事件打架。

调用图:调用 6 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, auto_summary, non_openai_model_provider);外部调用 7 个(default, new, assert!, assert_eq!, matches!, skip_if_no_network!, vec!)。

snapshot_request_shape_mid_turn_continuation_compaction3550–3654 ↗
async fn snapshot_request_shape_mid_turn_continuation_compaction()

作用:用快照测试“同一轮对话中工具输出后触发自动压缩”的请求形状。它确保工具结果先进入压缩请求,压缩后同一轮能继续。

数据流:模拟模型先发工具调用且 token 超限 → 自动压缩返回摘要 → 后续继续回复 → 检查第一请求、压缩请求和压缩后请求,并保存快照。

调用关系:它调用 auto_summary 和 format_labeled_requests_snapshot,覆盖真正的 mid-turn 继续压缩。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, auto_summary, non_openai_model_provider);外部调用 6 个(default, assert!, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。

auto_compact_clamps_config_limit_to_context_window3657–3714 ↗
async fn auto_compact_clamps_config_limit_to_context_window()

作用:测试如果用户配置的自动压缩上限比模型上下文窗口还大,系统会把有效上限夹到上下文窗口内。否则永远等不到压缩就会爆窗。

数据流:设置窗口 100、配置上限 200 → 第一轮用量超过可用窗口 → 下一轮触发自动压缩 → 检查压缩请求包含提示词。

调用关系:它用单次 mock 捕获第一轮和压缩请求,覆盖配置保护逻辑。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, auto_summary, non_openai_model_provider);外部调用 3 个(assert!, skip_if_no_network!, vec!)。

auto_compact_body_after_prefix_ignores_starting_window_prefix3717–3783 ↗
async fn auto_compact_body_after_prefix_ignores_starting_window_prefix()

作用:测试 body-after-prefix 模式不会因为会话一开始的固定前缀很大就马上压缩。它只关注前缀之后新增长的正文。

数据流:配置 body-after-prefix 上限 → 前两轮虽然 input_tokens 大但只建立基线,不压缩 → 第三轮增长超过预算 → 检查出现压缩请求。

调用关系:它使用 ev_completed_with_usage 模拟输入/输出 token 明细,验证预算口径。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

auto_compact_body_after_prefix_counts_growth_after_compaction3786–3886 ↗
async fn auto_compact_body_after_prefix_counts_growth_after_compaction()

作用:测试 body-after-prefix 模式在一次压缩后,会重新建立基线,并继续统计后续增长。这样多次压缩不会失去预算判断。

数据流:准备六段响应 → 第一轮后触发一次压缩,之后两轮建立并增长新窗口 → 第四轮再次超预算 → 断言第二次压缩出现。

调用关系:它和前一个测试一起覆盖 body-after-prefix 的基线重置和增长累计。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

auto_compact_body_after_prefix_still_caps_at_context_window3889–3945 ↗
async fn auto_compact_body_after_prefix_still_caps_at_context_window()

作用:测试 body-after-prefix 模式即使配置预算很大,也仍受模型总上下文窗口限制。总窗口快满时必须压缩。

数据流:设置上下文窗口 100、body 预算 200 → 连续提交三轮 → 第三轮前因总上下文接近窗口触发压缩 → 检查压缩提示词存在。

调用关系:它覆盖“正文预算”和“总窗口上限”同时存在时的安全边界。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

auto_compact_counts_encrypted_reasoning_before_last_user3948–4078 ↗
async fn auto_compact_counts_encrypted_reasoning_before_last_user()

作用:测试自动压缩计算历史大小时,会计入最后一个用户消息之前的加密推理内容,但不会被最后用户之后的推理误导。

数据流:构造第一轮和第二轮的加密推理内容 → 第三轮前应触发远程压缩 → 检查 compact 接口调用一次,第三个普通请求带压缩摘要和加密 compaction 项。

调用关系:它使用 ChatGPT 认证和 mount_compact_json_once,覆盖远程压缩对推理内容的计数。

调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_sequence, sse, start_mock_server, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert!, assert_eq!, wait_for_event, format!, json!, skip_if_no_network!, vec!)。

auto_compact_runs_when_reasoning_header_clears_between_turns4081–4166 ↗
async fn auto_compact_runs_when_reasoning_header_clears_between_turns()

作用:测试服务端曾声明“包含推理内容”的响应头消失后,自动压缩仍能正确运行。也就是头信息变化不能卡住压缩判断。

数据流:第一轮响应带 X-Reasoning-Included 头,后面响应不带 → 连续三轮用户输入 → 检查远程 compact 接口最终被调用一次。

调用关系:它用 mount_response_sequence 挂载带头和不带头的响应,覆盖服务端响应头状态切换。

调用图:调用 6 个内部函数(mount_compact_json_once, mount_response_sequence, sse, start_mock_server, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!)。

snapshot_request_shape_pre_turn_compaction_including_incoming_user_message4170–4287 ↗
async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_message()

作用:用快照记录“新用户输入到来前触发压缩”的当前请求形状。当前行为是压缩请求里不包含这次刚来的用户消息,但压缩后的跟进请求会包含它。

数据流:先提交两轮造成超限 → 改变线程上下文,再提交带图片和文字的第三轮 → 收集压缩请求和跟进请求 → 快照并断言第三轮文字、图片只在跟进请求里。

调用关系:它调用 local_selections 修改环境选择,并用 format_labeled_requests_snapshot 保存形状。

调用图:调用 6 个内部函数(mount_sse_sequence, sse, start_mock_server, local_selections, test_codex, non_openai_model_provider);外部调用 9 个(default, assert!, assert_eq!, submit_thread_settings, test_path_buf, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch4292–4392 ↗
async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch()

作用:测试预回合压缩时,如果新输入里包含模型切换标记,压缩请求会先去掉它,压缩后的真正请求再放回。这样压缩不会被新模型切换上下文污染。

数据流:先用旧模型对话,再提交切模型输入并触发压缩 → 检查三次请求 → 断言压缩请求有提示词但没有 <model_switch>,跟进请求有 <model_switch>。

调用关系:它用 disabled_permission_user_turn 构造模型切换输入,并生成快照记录当前行为。

调用图:调用 6 个内部函数(mount_sse_sequence, start_mock_server, test_codex, disabled_permission_user_turn, non_openai_model_provider, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(assert!, assert_eq!, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_request_shape_pre_turn_compaction_context_window_exceeded4395–4485 ↗
async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded()

作用:测试预回合自动压缩如果一直因为上下文窗口超限失败,会给用户明确错误,而不是继续发送超大请求。

数据流:第一轮普通对话后,第二轮前触发压缩,但后续多个压缩响应都返回 context_length_exceeded → 等错误事件和回合完成 → 快照压缩请求并检查错误文案。

调用关系:它用 sse_failed 模拟连续超窗失败,覆盖失败提示和请求形状。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 7 个(default, assert!, wait_for_event, wait_for_event_match, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_request_shape_manual_compact_without_previous_user_messages4488–4549 ↗
async fn snapshot_request_shape_manual_compact_without_previous_user_messages()

作用:测试没有任何旧用户消息时手动 /compact 的当前行为。系统仍会发一次压缩请求,后续对话带着规范上下文继续。

数据流:直接提交 Compact → 再提交一次用户输入 → 收集两次请求 → 用快照记录空历史压缩和后续请求形状。

调用关系:它是边界场景测试,防止以后无意改变“空历史也发压缩请求”的现有行为。

调用图:调用 5 个内部函数(mount_sse_sequence, sse, start_mock_server, test_codex, non_openai_model_provider);外部调用 6 个(default, assert_eq!, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。

manual_compaction_keeps_the_creation_time_global_instructions4552–4628 ↗
async fn manual_compaction_keeps_the_creation_time_global_instructions() -> Result<()>

作用:测试手动压缩前后都使用会话创建时读到的全局说明,而不是文件后来被改写后的内容。

数据流:创建旧 AGENTS.md 并启动会话 → 第一轮后把同一路径文件改成新内容 → 手动压缩并继续对话 → 检查三次请求都包含旧说明片段。

调用关系:它调用 write_global_file、local_compaction_provider、expected_instruction_fragment 和 assert_single_instruction_fragment,覆盖全局说明快照。

调用图:调用 7 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_instruction_fragment, expected_instruction_fragment, local_compaction_provider, write_global_file);外部调用 6 个(clone, new, new, assert_eq!, wait_for_event, vec!)。

mid_turn_compaction_keeps_the_creation_time_global_instructions4631–4700 ↗
async fn mid_turn_compaction_keeps_the_creation_time_global_instructions() -> Result<()>

作用:测试中途自动压缩也不会重新加载新的全局说明。即使压缩前新增了优先级更高的 override 文件,当前线程仍用创建时快照。

数据流:启动时写旧全局说明 → 会话创建后写新 override 文件 → 提交会触发中途压缩的用户输入 → 检查初始、压缩、继续请求都包含旧说明。

调用关系:它和手动全局说明测试互补,覆盖自动 mid-turn 压缩路径。

调用图:调用 7 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_instruction_fragment, expected_instruction_fragment, local_compaction_provider, write_global_file);外部调用 6 个(clone, new, new, assert_eq!, assert_ne!, vec!)。

remote_v2_compaction_keeps_creation_time_instructions_after_same_path_mutation4703–4834 ↗
async fn remote_v2_compaction_keeps_creation_time_instructions_after_same_path_mutation() -> Result<()>

作用:测试远程 v2 压缩、保存 rollout、冷恢复之后,仍然重放创建时的旧全局说明快照。即使同一个文件路径内容已经被改成新说明,也不能污染旧会话。

数据流:创建旧说明并启动会话 → 改写同一路径文件 → 远程 v2 压缩并继续对话,刷出 rollout → 检查请求和 replacement_history → 关机后冷恢复,再提交一轮并确认仍使用旧说明历史。

调用关系:它调用 remote_v2_compaction_response、replacement_history_from_rollout、write_global_file 和说明断言辅助函数,是全局说明与远程压缩恢复的综合测试。

调用图:调用 8 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_instruction_fragment, expected_instruction_fragment, replacement_history_from_rollout, write_global_file, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(clone, new, new, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

core/tests/suite/compact_remote_parity.rs源码 ↗
test测试运行时,尤其是远程压缩功能的集成测试和回归检查

这里测的是 Codex 在对话太长时把历史“压缩成摘要”的功能。旧版会调用专门的 /responses/compact 接口,新版 V2 则把压缩当成普通 /responses 请求里的一种输出项。文件会搭一个假的 Codex 运行环境和假的服务器,喂进去不同对话场景:普通助手回复、推理内容、函数工具、shell 工具、图片、网页搜索等。然后它分别用旧版和新版跑一遍,把真正发出去的 JSON 请求、压缩后写入 rollout 文件的替换历史、压缩后的下一轮请求拿出来比较。为了避免随机路径、UUID、运行耗时这类“噪音”导致误判,它还会先把这些值标准化。这里也专门检查 API key 登录时 service_tier 的差异,以及手动压缩前后 hooks(钩子,事件发生时自动执行的小脚本)收到的内容是否一致。

函数细节53
AuthCase::build44–49 ↗
fn build(self) -> CodexAuth

作用:把测试里选择的登录方式变成 Codex 真正能使用的认证对象。这样同一套测试可以分别模拟 ChatGPT 登录和 API key 登录。

数据流:进去的是 AuthCase 里的一个选项 → 如果是 ChatGpt,就造一个测试用的假 ChatGPT 登录;如果是 ApiKey,就用字符串 dummy 造一个 API key 登录 → 出来的是 CodexAuth,后面搭测试环境时会塞进去。

调用关系:它被构建测试环境的流程间接使用;build_harness_inner 在设置测试 Codex 时会通过 RunSettings 里的 auth 调到它,确保每个场景用指定的认证方式启动。

调用图:调用 2 个内部函数(create_dummy_chatgpt_auth_for_testing, from_api_key)。

RunSettings::default59–64 ↗
fn default() -> Self

作用:给测试提供一套默认运行参数:用 ChatGPT 假登录,不开启 fast 服务档位。多数测试不关心这些细节时就用它。

数据流:没有输入 → 创建一个 RunSettings,auth 设成 ChatGpt,service_tier_fast 设成 false → 返回这份默认设置。

调用关系:remote_compaction_parity_manual_transcripts、run_manual_hook_session 和 build_auto_harness 会用它,避免每个测试都重复写同样的默认配置。

调用图:被 3 处调用(build_auto_harness, remote_compaction_parity_manual_transcripts, run_manual_hook_session)。

Step::label78–87 ↗
fn label(self) -> &'static str

作用:把一个测试步骤变成稳定的文字标签,比如 assistant 或 shell_tool。这个标签会放进用户输入和模拟响应的名字里,方便追踪是哪一步。

数据流:进去的是 Step 枚举值 → 根据是哪种对话步骤选一个固定字符串 → 返回这个字符串。

调用关系:它主要被生成用户输入和响应内容的辅助函数使用,让 run_manual_session 里每一步都有清楚、可重复的标记。

remote_compaction_parity_manual_transcripts118–145 ↗
async fn remote_compaction_parity_manual_transcripts() -> Result<()>

作用:这是一个测试入口,检查多种“手动触发压缩”的对话场景下,新旧远程压缩是否等价。没有它,普通聊天、工具调用、图片等混合历史可能在 V2 中悄悄变样。

数据流:进去没有业务输入,测试开始时先判断是否能联网,不行就跳过 → 准备四个场景,每个场景包含不同 Step 组合 → 逐个交给 compare_manual_scenario 比较旧版和 V2,最后返回测试成功或错误。

调用关系:它是顶层 tokio 异步测试,会调用 RunSettings::default 拿默认配置,再调用 compare_manual_scenario 去真正跑旧版和新版会话。

调用图:调用 2 个内部函数(default, compare_manual_scenario);外部调用 1 个(skip_if_no_network!)。

remote_compaction_parity_v2_api_key_sends_service_tier_upgrade148–176 ↗
async fn remote_compaction_parity_v2_api_key_sends_service_tier_upgrade() -> Result<()>

作用:这个测试专门确认 API key 登录时,V2 压缩请求会带上 fast 服务档位,而旧版保持不带。也就是说,它既允许这个有意变化,又检查除此之外其他内容不能乱变。

数据流:进去没有业务输入,先跳过无网络环境 → 创建一个用 API key 且开启 fast 档位的场景 → 分别运行旧版和 V2 → 检查旧版 compact 请求没有 service_tier,V2 有 fast,再比较其余压缩请求、后续请求和历史记录。

调用关系:它直接调用 run_manual_session 生成两份 Capture;再把结果交给 assert_compact_requests_eq_except_v2_service_tier 和 assert_follow_up_and_history_eq 做针对性比较。

调用图:调用 3 个内部函数(assert_compact_requests_eq_except_v2_service_tier, assert_follow_up_and_history_eq, run_manual_session);外部调用 2 个(assert_eq!, skip_if_no_network!)。

remote_compaction_parity_manual_hooks179–186 ↗
async fn remote_compaction_parity_manual_hooks() -> Result<()>

作用:这个测试检查手动压缩时,旧版和 V2 触发的前置、后置钩子收到的 payload 是否一致。钩子就像门铃,压缩前后会通知外部脚本;这个测试防止新版通知内容变了。

数据流:进去没有业务输入,先确认网络可用 → 分别用旧版和 V2 跑带 hooks 的手动压缩会话 → 读取两个钩子日志的简化视图 → 用 JSON 比较确认一致。

调用关系:它把主要工作交给 run_manual_hook_session,最后用 assert_json_eq 给出更清楚的差异错误。

调用图:调用 2 个内部函数(assert_json_eq, run_manual_hook_session);外部调用 1 个(skip_if_no_network!)。

remote_compaction_parity_pre_turn_auto189–196 ↗
async fn remote_compaction_parity_pre_turn_auto() -> Result<()>

作用:这个测试检查“下一轮开始前自动压缩”的场景,新旧实现是否表现一致。它防止系统在自动整理历史时改变下一次请求看到的上下文。

数据流:进去没有业务输入,先跳过无网络环境 → 旧版和 V2 各跑一次 pre-turn 自动压缩会话 → 得到两份 Capture → 交给 assert_capture_eq 比较。

调用关系:它是顶层测试入口,调用 run_pre_turn_auto_session 做实际会话,再用通用比较函数 assert_capture_eq 判断是否等价。

调用图:调用 2 个内部函数(assert_capture_eq, run_pre_turn_auto_session);外部调用 1 个(skip_if_no_network!)。

remote_compaction_parity_mid_turn_auto199–206 ↗
async fn remote_compaction_parity_mid_turn_auto() -> Result<()>

作用:这个测试检查“正在一轮对话中,因为工具调用等情况触发自动压缩”的场景,新旧实现是否一致。它覆盖比普通回合更容易出错的中途压缩。

数据流:进去没有业务输入,先确认网络可用 → 分别运行旧版和 V2 的 mid-turn 自动压缩流程 → 拿到请求和历史记录 → 比较两边是否等价。

调用关系:它调用 run_mid_turn_auto_session 构造中途工具调用场景,然后复用 assert_capture_eq 做完整比对。

调用图:调用 2 个内部函数(assert_capture_eq, run_mid_turn_auto_session);外部调用 1 个(skip_if_no_network!)。

compare_manual_scenario208–213 ↗
async fn compare_manual_scenario(scenario: &Scenario, settings: RunSettings) -> Result<()>

作用:对某一个手动压缩场景,跑一遍旧版再跑一遍 V2,然后比较两边结果。它是多个手动场景共用的小调度员。

数据流:进去的是一个 Scenario 和运行设置 → 用这些参数分别调用 run_manual_session,得到 legacy 和 v2 两份 Capture → 调用 assert_capture_eq 检查,最后返回成功或错误。

调用关系:remote_compaction_parity_manual_transcripts 会对每个场景调用它;它自己不做细节判断,而是把执行交给 run_manual_session,把比较交给 assert_capture_eq。

调用图:调用 2 个内部函数(assert_capture_eq, run_manual_session);被 1 处调用(remote_compaction_parity_manual_transcripts)。

assert_capture_eq215–255 ↗
fn assert_capture_eq(label: &str, legacy: &Capture, v2: &Capture)

作用:比较一次旧版和 V2 压缩运行的核心结果是否一样。它会检查压缩请求、压缩后的后续请求、写入历史文件的替换历史。

数据流:进去的是场景标签、legacy Capture 和 v2 Capture → 先确认旧版调用 compact 接口一次、V2 不调用旧 compact 接口 → 抽取并标准化 compact 请求、follow-up 请求和 replacement history → 如果不一致就报错,一致时打印一行统计信息。

调用关系:compare_manual_scenario、remote_compaction_parity_pre_turn_auto 和 remote_compaction_parity_mid_turn_auto 都靠它做最终判断;它内部调用 compact_request_view、follow_up_request_view 和 assert_json_eq。

调用图:调用 3 个内部函数(assert_json_eq, compact_request_view, follow_up_request_view);被 3 处调用(compare_manual_scenario, remote_compaction_parity_mid_turn_auto, remote_compaction_parity_pre_turn_auto);外部调用 3 个(assert_eq!, format!, println!)。

assert_compact_requests_eq_except_v2_service_tier257–275 ↗
fn assert_compact_requests_eq_except_v2_service_tier(label: &str, legacy: &Capture, v2: &Capture)

作用:比较旧版和 V2 的压缩请求,但允许 V2 多一个 service_tier 字段。它用于测试一个“预期中的差异”,防止把有意升级误判成失败。

数据流:进去的是标签和两份 Capture → 检查旧版 compact 端点调用次数和 V2 调用次数 → 抽取两边压缩请求视图 → 从 V2 视图里删掉 service_tier → 再比较剩余 JSON 是否一致。

调用关系:remote_compaction_parity_v2_api_key_sends_service_tier_upgrade 专门调用它,配合 remove_object_field 和 assert_json_eq 完成“除了这个字段都一样”的检查。

调用图:调用 3 个内部函数(assert_json_eq, compact_request_view, remove_object_field);被 1 处调用(remote_compaction_parity_v2_api_key_sends_service_tier_upgrade);外部调用 2 个(assert_eq!, format!)。

assert_follow_up_and_history_eq277–291 ↗
fn assert_follow_up_and_history_eq(label: &str, legacy: &Capture, v2: &Capture)

作用:只比较压缩后的下一次请求和替换历史是否一致。它用于 service_tier 特例测试里,确认除了压缩请求字段变化,后续行为没有被影响。

数据流:进去的是标签和两份 Capture → 抽取两边 follow-up 请求的稳定视图 → 比较它们 → 再比较 replacement_history → 不一致就报错。

调用关系:remote_compaction_parity_v2_api_key_sends_service_tier_upgrade 在检查完 compact 请求特例后调用它,保证后续请求和本地记录仍然一致。

调用图:调用 2 个内部函数(assert_json_eq, follow_up_request_view);被 1 处调用(remote_compaction_parity_v2_api_key_sends_service_tier_upgrade);外部调用 1 个(format!)。

run_manual_session293–336 ↗
async fn run_manual_session(
    scenario: &Scenario,
    mode: Mode,
    settings: RunSettings,
) -> Result<Capture>

作用:实际跑一场“用户手动要求压缩”的测试会话。它负责搭环境、喂用户输入、触发压缩、再发压缩后的下一条消息,并收集结果。

数据流:进去的是场景、模式和运行设置 → 根据场景准备一串假服务器响应;V2 额外准备一次 compaction 输出;搭建 harness;挂上假 responses 服务和旧版 compact 服务 → 按步骤提交用户输入,再提交 Op::Compact,之后再提交一条压缩后的用户消息 → 调用 capture_from_requests 汇总请求和历史。

调用关系:compare_manual_scenario 和 service_tier 测试都会调用它;它把准备响应交给 response_bodies_for_scenario、after_compact_response_body、compaction_v2_response_body,把环境交给 build_harness,把收尾采集交给 capture_from_requests。

调用图:调用 12 个内部函数(mount_sse_sequence, after_compact_response_body, build_harness, capture_from_requests, compaction_v2_response_body, follow_up_index, mount_legacy_compact_if_needed, response_bodies_for_scenario, rollout_path, submit_user_input (+2 more));被 2 处调用(compare_manual_scenario, remote_compaction_parity_v2_api_key_sends_service_tier_upgrade);外部调用 1 个(vec!)。

run_pre_turn_auto_session338–394 ↗
async fn run_pre_turn_auto_session(mode: Mode) -> Result<Capture>

作用:跑一场“回合开始前自动压缩”的测试会话。它模拟先聊一轮,再在下一次用户输入前因为 token 限制触发压缩。

数据流:进去的是 Legacy 或 V2 模式 → 准备模式对应的假响应列表;搭自动压缩限制很低的 harness;挂服务器 mock → 提交第一条和第二条用户输入 → 从请求、compact mock 和 rollout 文件里收集 Capture。

调用关系:remote_compaction_parity_pre_turn_auto 调用它;它依赖 build_auto_harness 打开自动压缩条件,并用 capture_from_requests 做统一采集。

调用图:调用 7 个内部函数(mount_sse_sequence, build_auto_harness, capture_from_requests, follow_up_index, mount_legacy_compact_if_needed, rollout_path, submit_user_input);被 1 处调用(remote_compaction_parity_pre_turn_auto);外部调用 1 个(vec!)。

run_mid_turn_auto_session396–444 ↗
async fn run_mid_turn_auto_session(mode: Mode) -> Result<Capture>

作用:跑一场“回合中途自动压缩”的测试会话。它用一次函数工具调用来制造中途状态,检查压缩不会破坏这类流程。

数据流:进去的是模式 → 准备一次工具调用响应和压缩后响应;V2 还插入 compaction 输出 → 搭 harness 和 mock 服务 → 提交一条用户输入 → 捕获 compact 请求、后续请求和 replacement history。

调用关系:remote_compaction_parity_mid_turn_auto 调用它;它和 run_pre_turn_auto_session 很像,只是模拟的触发时机和响应内容不同。

调用图:调用 7 个内部函数(mount_sse_sequence, build_auto_harness, capture_from_requests, follow_up_index, mount_legacy_compact_if_needed, rollout_path, submit_user_input);被 1 处调用(remote_compaction_parity_mid_turn_auto);外部调用 1 个(vec!)。

run_manual_hook_session446–487 ↗
async fn run_manual_hook_session(mode: Mode) -> Result<Value>

作用:跑一场带钩子的手动压缩会话,并把钩子收到的内容读回来。它用来比较旧版和 V2 对外部脚本的通知是否相同。

数据流:进去的是模式 → 准备一次普通回复,V2 额外准备压缩响应 → 构建启用 hooks 的 harness → 提交用户输入,手动提交 Compact,等待完成 → 如果是旧版,确认 compact mock 被调用一次 → 读取 pre 和 post 两个日志文件,组合成 JSON 返回。

调用关系:remote_compaction_parity_manual_hooks 调用它;它用 build_harness 写入钩子配置,用 hook_log_view 把日志转成适合比较的简化结构。

调用图:调用 7 个内部函数(mount_sse_sequence, default, build_harness, hook_log_view, mount_legacy_compact_if_needed, submit_user_input, wait_for_turn_complete);被 1 处调用(remote_compaction_parity_manual_hooks);外部调用 3 个(assert_eq!, json!, vec!)。

build_auto_harness489–497 ↗
async fn build_auto_harness(mode: Mode) -> Result<TestCodexHarness>

作用:创建一个会自动压缩的测试 Codex 环境。这里把 token 上限设得很低,让测试更容易触发压缩。

数据流:进去的是模式 → 使用默认运行设置、不开 hooks、自动压缩阈值为 200 → 调用 build_harness_inner → 返回 TestCodexHarness。

调用关系:run_pre_turn_auto_session 和 run_mid_turn_auto_session 会调用它;它只是给 build_harness_inner 填好自动压缩专用参数。

调用图:调用 2 个内部函数(default, build_harness_inner);被 2 处调用(run_mid_turn_auto_session, run_pre_turn_auto_session)。

build_harness499–501 ↗
async fn build_harness(mode: Mode, settings: RunSettings, hooks: bool) -> Result<TestCodexHarness>

作用:创建普通的测试 Codex 环境,可选择是否启用手动压缩 hooks。它是手动场景和 hook 场景的环境入口。

数据流:进去的是模式、运行设置和是否启用 hooks → 把自动压缩限制留空 → 调用 build_harness_inner → 返回搭好的 harness。

调用关系:run_manual_session 和 run_manual_hook_session 调用它;真正的配置细节都集中在 build_harness_inner。

调用图:调用 1 个内部函数(build_harness_inner);被 2 处调用(run_manual_hook_session, run_manual_session)。

build_harness_inner503–537 ↗
async fn build_harness_inner(
    mode: Mode,
    settings: RunSettings,
    hooks: bool,
    auto_compact_limit: Option<i64>,
) -> Result<TestCodexHarness>

作用:真正搭好测试用 Codex 环境,包括工作目录、认证、全局说明、模型配置、功能开关和 hooks 信任设置。它相当于测试工厂。

数据流:进去的是模式、运行设置、hooks 开关和自动压缩阈值 → 创建固定工作目录,写 AGENTS.md,全局设置 cwd、developer instructions、service_tier、自动压缩限制;需要 hooks 时写 hook 文件并信任;Legacy 模式禁用 RemoteCompactionV2 功能 → 返回 TestCodexHarness。

调用关系:build_auto_harness 和 build_harness 都委托它;它调用 test_codex 和 with_builder 来把配置灌进测试框架。

调用图:调用 2 个内部函数(with_builder, test_codex);被 2 处调用(build_auto_harness, build_harness);外部调用 1 个(create_dir_all)。

rollout_path539–546 ↗
fn rollout_path(harness: &TestCodexHarness) -> PathBuf

作用:从测试环境里取出 rollout 文件路径。rollout 文件像会话流水账,里面能找到压缩后替换历史。

数据流:进去的是 TestCodexHarness → 读取测试对象里的 session_configured.rollout_path → 克隆并返回这个路径;如果没有路径就直接失败。

调用关系:run_manual_session、run_pre_turn_auto_session 和 run_mid_turn_auto_session 在采集结果前调用它,后面 capture_from_requests 会用这个路径读 replacement history。

调用图:调用 1 个内部函数(test);被 3 处调用(run_manual_session, run_mid_turn_auto_session, run_pre_turn_auto_session)。

mount_legacy_compact_if_needed548–559 ↗
async fn mount_legacy_compact_if_needed(
    harness: &TestCodexHarness,
    mode: Mode,
) -> Option<ResponseMock>

作用:只有旧版模式才挂上假的 /responses/compact 接口。V2 不应该调用这个旧接口,所以这里会返回空。

数据流:进去的是 harness 和模式 → 如果是 Legacy,就在假服务器上挂一个只返回 SUMMARY 的 compact mock,并返回它;如果是 V2,返回 None → 后续用这个结果判断有没有错误调用旧接口。

调用关系:各个会话运行函数都会调用它;capture_from_requests 会根据这里是否有 mock 来选择从哪里取 compact 请求。

调用图:调用 2 个内部函数(mount_compact_user_history_with_summary_once, server);被 4 处调用(run_manual_hook_session, run_manual_session, run_mid_turn_auto_session, run_pre_turn_auto_session)。

follow_up_index561–563 ↗
fn follow_up_index(request_count: usize) -> usize

作用:算出“压缩后的下一次普通请求”在请求列表里的位置。它默认 follow-up 是当前 responses 请求列表的最后一个。

数据流:进去的是请求总数 → 用总数减一得到最后一个下标 → 返回这个下标;如果请求数是零就失败。

调用关系:run_manual_session、run_pre_turn_auto_session 和 run_mid_turn_auto_session 在调用 capture_from_requests 前用它定位 follow-up 请求。

调用图:被 3 处调用(run_manual_session, run_mid_turn_auto_session, run_pre_turn_auto_session)。

capture_from_requests565–603 ↗
async fn capture_from_requests(
    mode: Mode,
    codex: &codex_core::CodexThread,
    rollout_path: &Path,
    responses_mock: &ResponseMock,
    compact_mock: Option<&ResponseMock>,
    follow_up_

作用:把一次测试会话里真正发出的关键数据收集成 Capture。它是“取证”函数:从 mock 请求和 rollout 文件里拿证据。

数据流:进去的是模式、Codex 线程、rollout 路径、responses mock、可选 compact mock 和 follow-up 下标 → 读取 responses 请求列表,取出 follow-up body;旧版从 compact mock 取 compact body 和次数,V2 从 follow-up 前一个 responses 请求取 compact body → 关闭 Codex,等待 ShutdownComplete → 读 rollout replacement history,返回 Capture。

调用关系:run_manual_session、run_pre_turn_auto_session 和 run_mid_turn_auto_session 都用它收尾;它会调用 replacement_history_from_rollout,并通过 submit 发送 Shutdown。

调用图:调用 3 个内部函数(submit, requests, replacement_history_from_rollout);被 3 处调用(run_manual_session, run_mid_turn_auto_session, run_pre_turn_auto_session);外部调用 2 个(wait_for_event, panic!)。

submit_user_input605–617 ↗
async fn submit_user_input(codex: &codex_core::CodexThread, items: Vec<UserInput>) -> Result<()>

作用:向测试中的 Codex 提交一轮用户输入,并等这一轮处理完成。它把“发消息”和“等回复结束”包装成一步。

数据流:进去的是 CodexThread 和一组 UserInput → 包成 Op::UserInput,带上默认的附加字段提交 → 等待 TurnComplete 事件 → 返回成功或错误。

调用关系:手动、自动和 hook 会话都会调用它;它内部调用 wait_for_turn_complete,保证后续测试不会在上一轮没结束时继续操作。

调用图:调用 2 个内部函数(submit, wait_for_turn_complete);被 4 处调用(run_manual_hook_session, run_manual_session, run_mid_turn_auto_session, run_pre_turn_auto_session);外部调用 1 个(default)。

wait_for_turn_complete619–621 ↗
async fn wait_for_turn_complete(codex: &codex_core::CodexThread)

作用:等待 Codex 发出“这一轮完成”的事件。测试用它来同步节奏,避免还没处理完就开始检查请求。

数据流:进去的是 CodexThread → 持续等待事件,直到看到 EventMsg::TurnComplete → 没有额外返回值。

调用关系:submit_user_input 和 run_manual_hook_session 会用它;它把底层等待逻辑交给 wait_for_event。

调用图:被 3 处调用(run_manual_hook_session, run_manual_session, submit_user_input);外部调用 1 个(wait_for_event)。

user_input_for_step623–636 ↗
fn user_input_for_step(scenario_name: &str, idx: usize, step: Step) -> Vec<UserInput>

作用:根据测试步骤生成对应的用户输入。图片步骤会先放一张测试图片,再放文字;其他步骤只放文字。

数据流:进去的是场景名、步骤序号和 Step → 如果 Step 是 ImageAssistant,就加入固定 IMAGE_URL;再加入包含场景名、序号和步骤标签的文本 → 返回 UserInput 列表。

调用关系:run_manual_session 在循环每个场景步骤时调用它;它使用 Step::label 让输入文本和响应名字能对上。

调用图:被 1 处调用(run_manual_session);外部调用 3 个(new, format!, matches!)。

response_bodies_for_scenario638–645 ↗
fn response_bodies_for_scenario(scenario: &Scenario) -> Vec<String>

作用:把一个场景里的所有步骤转换成一串假服务器响应。测试服务器会按这个顺序把响应吐给 Codex。

数据流:进去的是 Scenario → 遍历里面的 steps 和序号 → 对每一步生成一个或多个 SSE 响应字符串 → 合并成 Vec<String> 返回。

调用关系:run_manual_session 用它准备普通对话阶段的响应;具体每种步骤怎么回,由 response_bodies_for_step 负责。

调用图:被 1 处调用(run_manual_session)。

response_bodies_for_step647–722 ↗
fn response_bodies_for_step(scenario_name: &str, idx: usize, step: Step) -> Vec<String>

作用:为某一种对话步骤生成模拟的 SSE 响应。SSE 是服务器持续推送事件的一种格式,这里用它假装模型在流式回复。

数据流:进去的是场景名、序号和 Step → 生成稳定的 response_id → 按步骤类型构造助手消息、推理项、函数调用、shell 调用、网页搜索完成事件等 → 返回一个或多个响应体字符串。

调用关系:它服务于 response_bodies_for_scenario;这些响应随后被 run_manual_session 挂到假服务器上,让 Codex 以为自己在和真实服务通信。

调用图:外部调用 2 个(format!, vec!)。

compaction_v2_response_body724–735 ↗
fn compaction_v2_response_body() -> String

作用:生成 V2 压缩请求的假响应。它告诉 Codex:远程服务返回了一个 compaction 输出,里面有加密摘要 SUMMARY。

数据流:没有输入 → 构造两个 SSE 事件:一个 response.output_item.done,类型是 compaction;一个 completed 事件 → 返回响应体字符串。

调用关系:run_manual_session 和 V2 自动压缩流程会把它插进响应序列;旧版不需要它,因为旧版走单独 compact mock。

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

after_compact_response_body737–745 ↗
fn after_compact_response_body(scenario_name: &str) -> String

作用:生成压缩完成后下一轮普通助手回复的假响应。它用来检查压缩后的历史能不能正确用于后续请求。

数据流:进去的是场景名 → 构造一条助手消息和一条 completed 事件,名字都带场景名 → 返回 SSE 响应体字符串。

调用关系:run_manual_session 和自动压缩会话会把它作为压缩后的 follow-up 响应;后面 assert_capture_eq 会检查这个 follow-up 请求是否一致。

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

compact_request_view747–767 ↗
fn compact_request_view(body: &Value, mode: Mode) -> Value

作用:从完整 compact 请求 JSON 里抽出适合比较的稳定部分。V2 会额外带一个 compaction_trigger,这里会确认并去掉它再比较。

数据流:进去的是请求 body 和模式 → 取出 input 数组;如果是 V2,弹出最后一项并确认它正好是 compaction_trigger → 选取 compact 相关字段,标准化 input 和其他值,最后排序成规范 JSON → 返回这个比较视图。

调用关系:assert_capture_eq 和 assert_compact_requests_eq_except_v2_service_tier 用它比较新旧压缩请求;它依赖 selected_request_fields、normalize_value 和 canonical_json。

调用图:调用 3 个内部函数(canonical_json, normalize_value, selected_request_fields);被 2 处调用(assert_capture_eq, assert_compact_requests_eq_except_v2_service_tier);外部调用 3 个(Array, get, assert_eq!)。

follow_up_request_view769–777 ↗
fn follow_up_request_view(body: &Value) -> Value

作用:从压缩后的下一次普通请求里抽出可比较的字段。它过滤掉不关心或不稳定的内容,只保留测试要保证一致的部分。

数据流:进去的是 follow-up 请求 body → 选出 FollowUp 模式下的重要字段 → 单独取 input 并标准化 → 排序成规范 JSON → 返回比较视图。

调用关系:assert_capture_eq 和 assert_follow_up_and_history_eq 用它比较压缩后的下一次请求;它也用 selected_request_fields、normalize_value 和 canonical_json。

调用图:调用 3 个内部函数(canonical_json, normalize_value, selected_request_fields);被 2 处调用(assert_capture_eq, assert_follow_up_and_history_eq);外部调用 1 个(get)。

replacement_history_from_rollout779–804 ↗
fn replacement_history_from_rollout(path: &Path) -> Result<Value>

作用:从 rollout 会话流水账里找出压缩时写入的替换历史。替换历史说明原来的长对话被哪些摘要内容替代。

数据流:进去的是 rollout 文件路径 → 读文件逐行解析 JSONL;找到 RolloutItem::Compacted 且 message 为空、带 replacement_history 的记录 → 把每个历史项转成 JSON,标准化并排序 → 返回 JSON 数组。

调用关系:capture_from_requests 会调用它,把磁盘上的会话记录纳入 Capture;之后 assert_capture_eq 会比较新旧 replacement_history。

调用图:调用 2 个内部函数(canonical_json, normalize_value);被 1 处调用(capture_from_requests);外部调用 2 个(Array, read_to_string)。

write_manual_compact_hooks806–834 ↗
fn write_manual_compact_hooks(home: &Path)

作用:为测试写入手动压缩的前置和后置 hook 配置。它让 Codex 在压缩前后执行两个小 Python 脚本,把收到的 payload 记录下来。

数据流:进去的是测试 home 目录 → 写 pre_compact_manual.py 和 post_compact_manual.py 两个脚本 → 生成 hooks.json,声明 PreCompact 和 PostCompact 的 command hook → 写入 home 目录。

调用关系:build_harness_inner 在 hooks 开启时通过 pre-build hook 使用它;它调用 write_hook_script 生成脚本,并通过 python_hook_command 生成命令字符串。

调用图:调用 1 个内部函数(write_hook_script);外部调用 3 个(join, write, json!)。

write_hook_script836–849 ↗
fn write_hook_script(script_path: &Path, log_path: &Path)

作用:写一个简单的 Python hook 脚本。脚本会从标准输入读取 JSON payload,并追加写到日志文件。

数据流:进去的是脚本路径和日志路径 → 拼出 Python 代码,代码会 json.load(sys.stdin) 并把排序后的 JSON 写成一行 → 把代码写到脚本文件。

调用关系:write_manual_compact_hooks 调用它两次,分别生成压缩前和压缩后的记录脚本。

调用图:被 1 处调用(write_manual_compact_hooks);外部调用 2 个(format!, write)。

python_hook_command851–853 ↗
fn python_hook_command(script_path: &Path) -> String

作用:把 Python 脚本路径变成 hooks 配置里可执行的命令字符串。它负责加上 python3 和引号。

数据流:进去的是脚本路径 → 格式化成 python3 "路径" → 返回这个命令字符串。

调用关系:write_manual_compact_hooks 在生成 hooks.json 时使用它,确保 Codex 能用命令方式运行 hook 脚本。

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

hook_log_view855–875 ↗
fn hook_log_view(path: &Path) -> Result<Value>

作用:读取 hook 日志,并把里面复杂的 payload 简化成稳定、可比较的视图。它不比较完整内容,只比较关键字段是否存在和几个核心值。

数据流:进去的是日志文件路径 → 读取每一行 JSON → 对每条记录提取 hook_event_name、trigger、model,并把 reason、phase、implementation、status、error 是否存在转成布尔值 → 返回 JSON 数组。

调用关系:run_manual_hook_session 调用它读取 pre 和 post 日志;remote_compaction_parity_manual_hooks 随后比较旧版和 V2 的简化结果。

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

selected_request_fields883–919 ↗
fn selected_request_fields(body: &Value, mode: SelectedFieldsMode) -> Value

作用:从请求 JSON 里挑出测试关心的字段。compact 请求和 follow-up 请求关注的字段不完全一样,所以这里按模式选择。

数据流:进去的是完整请求 body 和 SelectedFieldsMode → 根据模式列出字段白名单 → 对存在的字段做 normalize_value,再放进新对象 → 返回只含关键字段的 JSON 对象。

调用关系:compact_request_view 和 follow_up_request_view 都调用它;它负责第一层“过滤”,后续再由 normalize_value 和 canonical_json 做稳定化。

调用图:调用 1 个内部函数(normalize_value);被 2 处调用(compact_request_view, follow_up_request_view);外部调用 3 个(Object, get, new)。

normalize_value921–932 ↗
fn normalize_value(value: Value) -> Value

作用:递归标准化 JSON 值,把里面不稳定的字符串处理掉。这样测试不会因为临时目录、UUID、耗时不同而失败。

数据流:进去的是任意 JSON 值 → 如果是字符串就调用 normalize_string;如果是数组或对象,就递归处理每个元素或字段;数字、布尔、空值保持原样 → 返回标准化后的 JSON。

调用关系:compact_request_view、follow_up_request_view、replacement_history_from_rollout 和 selected_request_fields 都依赖它做比较前清洗。

调用图:调用 1 个内部函数(normalize_string);被 4 处调用(compact_request_view, follow_up_request_view, replacement_history_from_rollout, selected_request_fields);外部调用 3 个(Array, Object, String)。

normalize_string934–964 ↗
fn normalize_string(value: &str) -> String

作用:把字符串里的随机内容替换成固定占位符。它主要处理 UUID、临时 skills 路径和 shell 输出里的 wall time。

数据流:进去的是字符串切片 → 如果像 UUID,就直接返回 <UUID>;否则复制成可修改字符串,替换 Linux/macOS/Windows 临时 skills 路径前缀,再扫描 Wall time: 数字 seconds 并把数字换成 <WALL_TIME> → 返回处理后的字符串。

调用关系:normalize_value 会对所有 JSON 字符串调用它;三个 normalize_string_rewrites_* 测试专门验证它的路径和耗时改写行为。

调用图:调用 2 个内部函数(is_uuid_like, normalize_tmp_prefix_before_marker);被 4 处调用(normalize_string_rewrites_linux_temp_skill_paths, normalize_string_rewrites_shell_wall_times, normalize_string_rewrites_windows_temp_skill_paths, normalize_value)。

is_uuid_like966–974 ↗
fn is_uuid_like(value: &str) -> bool

作用:判断一个字符串是不是标准 UUID 形状。UUID 是常见的随机标识符,如果直接比较会让测试不稳定。

数据流:进去的是字符串 → 检查长度是否 36,固定位置是否是短横线,其他位置是否都是十六进制字符 → 返回 true 或 false。

调用关系:normalize_string 首先调用它;如果确认是 UUID,后面就统一替换成 <UUID>。

调用图:被 1 处调用(normalize_string)。

normalize_tmp_prefix_before_marker976–1004 ↗
fn normalize_tmp_prefix_before_marker(text: &mut String, marker: &str)

作用:把临时目录中 skills 文件夹前面的随机路径替换成 <CODEX_HOME>。这解决不同操作系统和每次测试临时目录不同的问题。

数据流:进去的是可修改字符串和 marker,比如 /skills/ 或 \skills\ → 循环查找 marker;在 marker 前面寻找 Linux、macOS、Windows 的临时目录模式 → 找到就把临时前缀替换成 <CODEX_HOME>,找不到就继续往后搜 → 直接修改原字符串。

调用关系:normalize_string 会分别用 Unix 风格和 Windows 风格 marker 调用它;相关单元测试验证 Linux 和 Windows 路径都能被改写。

调用图:被 1 处调用(normalize_string)。

normalize_string_rewrites_linux_temp_skill_paths1007–1018 ↗
fn normalize_string_rewrites_linux_temp_skill_paths()

作用:这是 normalize_string 的单元测试,确认 Linux/macOS 风格临时 skills 路径会被替换成 <CODEX_HOME>。它防止路径标准化逻辑退化。

数据流:进去没有外部输入 → 构造包含 /tmp 和 /private/tmp skills 路径的字符串 → 调用 normalize_string → 断言输出里的随机临时前缀已变成 <CODEX_HOME>。

调用关系:它独立测试 normalize_string;失败时说明 compact 请求比较可能会被临时路径干扰。

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

normalize_string_rewrites_windows_temp_skill_paths1021–1032 ↗
fn normalize_string_rewrites_windows_temp_skill_paths()

作用:这是 normalize_string 的单元测试,确认 Windows 风格临时 skills 路径也会被稳定化。它保证测试在 Windows 跑时不会因为路径不同而误报。

数据流:进去没有外部输入 → 构造包含 C:/Users/.../Temp 和反斜杠路径的字符串 → 调用 normalize_string → 断言结果保留 skills 后半段,但前缀替换成 <CODEX_HOME>。

调用关系:它直接覆盖 normalize_string 和 normalize_tmp_prefix_before_marker 的 Windows 分支。

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

normalize_string_rewrites_shell_wall_times1035–1046 ↗
fn normalize_string_rewrites_shell_wall_times()

作用:这是 normalize_string 的单元测试,确认 shell 输出里的运行耗时会被替换成固定占位符。耗时每次都可能不同,不能直接参与比较。

数据流:进去没有外部输入 → 构造含 Wall time: 0 seconds 和 Wall time: 0.1 seconds 的字符串 → 调用 normalize_string → 断言数字部分都变成 <WALL_TIME>。

调用关系:它验证 normalize_string 的 wall time 清洗逻辑;这对 shell 工具场景的 parity 比较很重要。

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

canonical_json1048–1063 ↗
fn canonical_json(value: &Value) -> Value

作用:把 JSON 对象的字段按名字排序,生成稳定的 JSON 结构。JSON 对象本来不保证字段顺序,排序后更适合比较和报错。

数据流:进去的是 JSON 值 → 如果是对象,就把键排序并递归处理值;如果是数组,就递归处理每个元素;其他值直接克隆 → 返回规范化后的 JSON。

调用关系:compact_request_view、follow_up_request_view 和 replacement_history_from_rollout 在返回比较结果前都会调用它。

调用图:被 3 处调用(compact_request_view, follow_up_request_view, replacement_history_from_rollout);外部调用 3 个(Array, Object, clone)。

remove_object_field1065–1069 ↗
fn remove_object_field(value: &mut Value, field: &str)

作用:从 JSON 对象里删除一个指定字段。这里主要用来临时去掉 V2 的 service_tier,再和旧版比较。

数据流:进去的是可修改 JSON 值和字段名 → 如果这个值是对象,就删除该字段;如果不是对象,就不做事 → 修改发生在传入的 JSON 上。

调用关系:assert_compact_requests_eq_except_v2_service_tier 调用它,完成“忽略这个字段”的特例比较。

调用图:被 1 处调用(assert_compact_requests_eq_except_v2_service_tier)。

assert_json_eq1071–1075 ↗
fn assert_json_eq(label: &str, left: &Value, right: &Value)

作用:比较两个 JSON 是否完全相等;如果不同,就给出第一处差异。它比普通断言更适合调试大 JSON。

数据流:进去的是错误标签、左边 JSON 和右边 JSON → 如果相等就什么都不做;如果不等,就调用 first_json_diff 找差异并 panic 报错。

调用关系:多个顶层比较函数都会调用它,包括 assert_capture_eq、service_tier 特例比较、hook parity 测试。

调用图:被 4 处调用(assert_capture_eq, assert_compact_requests_eq_except_v2_service_tier, assert_follow_up_and_history_eq, remote_compaction_parity_manual_hooks);外部调用 1 个(panic!)。

first_json_diff1077–1131 ↗
fn first_json_diff(left: &Value, right: &Value, path: &str) -> String

作用:找出两个 JSON 第一处不一样的位置,并写成容易读的文字。它像在两份表格里逐格找第一个不同。

数据流:进去的是左右 JSON 和当前路径字符串 → 如果都是对象,就按键排序逐个比;如果都是数组,就按下标比;长度不同或类型/值不同就生成说明 → 返回差异描述字符串。

调用关系:assert_json_eq 在发现 JSON 不等时调用它,让测试失败信息指出具体路径,而不是只说整体不一样。

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

short_json1133–1142 ↗
fn short_json(value: &Value) -> String

作用:把一个 JSON 值转成短一点的字符串,避免错误信息太长。超过上限时只保留前面一段并标注总长度。

数据流:进去的是 JSON 值 → 序列化成一行字符串 → 如果不超过 1000 字符就原样返回;否则截取前 1000 个字符并追加长度提示 → 返回字符串。

调用关系:first_json_diff 在报告某个值不同时用它,让 panic 信息既有内容又不至于刷屏。

调用图:外部调用 2 个(format!, to_string)。

compact_input_len1144–1154 ↗
fn compact_input_len(body: &Value, mode: Mode) -> usize

作用:计算 compact 请求里的 input 条目数量。V2 会多一个 compaction_trigger,所以这里会把它减掉,得到可和旧版比较的长度。

数据流:进去的是请求 body 和模式 → 读取 input 数组长度,没有就当 0 → Legacy 直接返回长度;V2 返回长度减一但不会低于 0。

调用关系:assert_capture_eq 成功时打印统计信息会用它,帮助看每个场景压缩请求里包含了多少输入项。

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

follow_up_input_len1156–1161 ↗
fn follow_up_input_len(body: &Value) -> usize

作用:计算压缩后 follow-up 请求里的 input 条目数量。它只用于打印测试统计,方便人工观察。

数据流:进去的是请求 body → 尝试读取 input 数组并取长度;如果没有 input 或不是数组,就返回 0。

调用关系:assert_capture_eq 在 parity 通过后调用它打印 follow_up_input_items。

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

replacement_history_len1163–1165 ↗
fn replacement_history_len(body: &Value) -> usize

作用:计算 replacement_history 里有多少条记录。它帮助测试日志显示压缩后替换历史的规模。

数据流:进去的是 JSON 值 → 如果它是数组就返回数组长度;否则返回 0。

调用关系:assert_capture_eq 成功打印 PARITY_OK 时调用它,和 compact_input_len、follow_up_input_len 一起输出简要统计。

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

core/tests/suite/compact_resume_fork.rs源码 ↗
testtest run

这份测试像是在给聊天系统做“历史记录压力测试”。它先用假的 SSE 服务(服务器持续推送事件的一种接口,这里用来假装模型在回复)代替真实模型,然后让 CodexThread 走完几种真实用户会遇到的流程:先聊天,再把旧对话压缩成摘要,再恢复磁盘里的会话,再从中间分叉出新分支,甚至回滚上一轮。每一步之后,测试都会抓住系统准备发给模型的 JSON 请求,检查里面的用户消息、摘要、开发者指令、工作目录变化等内容有没有按预期出现。可以把它想成查账:对话文件是账本,压缩和恢复是重新整理账本,分叉和回滚是改账本分支;这些测试确保最后拿给模型看的账没有串行、漏账或重复记账。

函数细节23
network_disabled48–50 ↗
fn network_disabled() -> bool

作用:检查当前测试环境是不是禁止联网。如果禁止联网,这些依赖本地模拟网络服务的测试会主动跳过,避免在沙箱里误报失败。

数据流:它读取一个环境变量 → 如果这个变量存在,就认为网络被禁用 → 返回 true 或 false,不改动任何状态。

调用关系:四个主要测试一开始都会先问它一句“现在能不能跑网络相关测试”。如果答案是不能,测试就打印提示并提前结束。

调用图:被 4 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view, snapshot_rollback_followup_turn_trims_context_updates, snapshot_rollback_past_compaction_replays_append_only_history);外部调用 1 个(var)。

body_contains_text52–54 ↗
fn body_contains_text(body: &str, text: &str) -> bool

作用:判断一段请求正文里是否包含某个文本的 JSON 写法。它不是简单搜原文,而是先按 JSON 规则处理转义,避免因为引号、换行等字符造成误判。

数据流:输入请求正文和要找的文字 → 先把文字交给 json_fragment 转成适合在 JSON 里搜索的片段 → 再检查正文是否包含这个片段 → 返回是否找到。

调用关系:它主要服务于 mock 请求匹配,尤其是在 mount_initial_flow 里判断某次请求是不是压缩摘要请求。它把具体的 JSON 转义细节交给 json_fragment。

调用图:调用 1 个内部函数(json_fragment)。

json_fragment56–61 ↗
fn json_fragment(text: &str) -> String

作用:把普通文字变成 JSON 字符串内部会出现的样子。比如有换行或引号时,JSON 会转义,这个函数帮测试按同一套规则生成可搜索片段。

数据流:输入一段普通文本 → 用 serde_json 序列化成 JSON 字符串 → 去掉最外层双引号 → 输出适合拿去 contains 搜索的文本片段。

调用关系:它被 body_contains_text 调用,是一个很小的工具函数,专门防止测试直接搜索原始文本时和 JSON 编码格式对不上。

调用图:被 1 处调用(body_contains_text);外部调用 1 个(to_string)。

normalize_line_endings_str63–69 ↗
fn normalize_line_endings_str(text: &str) -> String

作用:把字符串里的 Windows 换行和老式 Mac 换行统一成普通的 \n。这样测试不会因为不同系统换行符不同而失败。

数据流:输入一段字符串 → 如果里面有 \r,就把 \r\n 和单独的 \r 都替换成 \n → 输出统一换行后的字符串;如果没有 \r,就原样返回。

调用关系:normalize_compact_prompts 会用它来比较压缩提示词。它让测试关注“内容是否一样”,而不是被操作系统换行格式干扰。

调用图:被 1 处调用(normalize_compact_prompts)。

extract_summary_user_text71–76 ↗
fn extract_summary_user_text(request: &Value, summary_text: &str) -> String

作用:从一次发给模型的请求里,找出包含摘要内容的那条用户消息。测试用它确认压缩后的摘要确实被放回了模型上下文。

数据流:输入一份 JSON 请求和期望出现的摘要文本 → 先取出所有 user 角色的消息文字 → 找到包含摘要文本的那一条 → 返回这条完整文字;如果找不到就让测试失败。

调用关系:两个压缩/恢复相关的大测试会调用它。它依赖 json_message_input_texts 先把请求里的用户消息抽出来。

调用图:调用 1 个内部函数(json_message_input_texts);被 2 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view)。

json_message_input_texts78–97 ↗
fn json_message_input_texts(request: &Value, role: &str) -> Vec<String>

作用:从发给模型的 JSON 请求里,按角色提取消息文本。比如取出所有 user 消息,或所有 developer 消息,方便测试逐条比对。

数据流:输入一份 JSON 请求和角色名 → 进入 input 数组 → 只保留 type 是 message、role 符合要求的项 → 从每项 content 的第一段里取 text → 输出字符串列表。

调用关系:多个测试用它检查模型实际能看到哪些话。extract_summary_user_text 也把它当作基础工具,用来找摘要消息。

调用图:被 3 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view, extract_summary_user_text);外部调用 1 个(get)。

normalize_compact_prompts99–124 ↗
fn normalize_compact_prompts(requests: &mut [Value])

作用:把请求列表里用于触发摘要的系统压缩提示清掉。这样后面的断言可以专注比较真正的对话历史,而不是被固定提示词干扰。

数据流:输入多份 JSON 请求 → 对每份请求的 input 数组做筛选 → 删除空的 user 消息,以及文本等于压缩提示词的 user 消息 → 原地修改这些 JSON 请求。

调用关系:两个压缩/恢复主测试在检查历史前会调用它。它内部用 normalize_line_endings_str 处理换行差异,避免提示词因为换行格式不同而没被识别。

调用图:调用 1 个内部函数(normalize_line_endings_str);被 2 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view)。

compact_resume_and_fork_preserve_model_history_view129–280 ↗
async fn compact_resume_and_fork_preserve_model_history_view()

作用:测试一次完整流程:先聊天,压缩,再继续聊,关掉后恢复,再从旧位置分叉。它要确认每一步发给模型看的历史都没有丢,也没有乱序。

数据流:它先检查网络是否可用 → 启动 mock 模型服务器并挂好五次假回复 → 创建测试会话 → 依次提交用户消息、压缩、恢复、分叉和新消息 → 收集所有模型请求 → 清理压缩提示 → 对比请求里的 input 和 user 文本顺序 → 如果历史不符合预期就失败。

调用关系:这是文件里的核心场景测试之一。它串起 mount_initial_flow、start_test_conversation、user_turn、compact_conversation、shutdown_conversation、resume_conversation、fork_thread、gather_request_bodies 等辅助函数,最后用 json_message_input_texts 和 extract_summary_user_text 做细查。

调用图:调用 13 个内部函数(compact_conversation, extract_summary_user_text, fetch_conversation_path, fork_thread, gather_request_bodies, json_message_input_texts, mount_initial_flow, network_disabled, normalize_compact_prompts, resume_conversation (+3 more));外部调用 6 个(start, assert!, assert_eq!, json!, println!, vec!)。

compact_resume_after_second_compaction_preserves_history285–417 ↗
async fn compact_resume_after_second_compaction_preserves_history() -> Result<()>

作用:测试更复杂的场景:对话压缩后恢复、分叉,再压缩一次,然后再恢复。它确认第二次压缩后的历史还能被正确接上新消息。

数据流:它先跳过禁网环境 → 挂一串固定顺序的假 SSE 回复 → 创建会话 → 执行第一次压缩、恢复、分叉、第二次压缩、再次恢复 → 抓取所有请求 JSON → 统一换行并移除压缩提示 → 比较第二次压缩前后的 input 前缀和用户消息结构 → 成功则返回 Ok。

调用关系:这是对第一个主测试的加压版本。它使用 mount_second_compact_sequence 保证请求按真实顺序记录,也复用 start_test_conversation、user_turn、compact_conversation、resume_conversation 和 fork_thread 等流程工具。

调用图:调用 12 个内部函数(compact_conversation, extract_summary_user_text, fetch_conversation_path, fork_thread, json_message_input_texts, mount_second_compact_sequence, network_disabled, normalize_compact_prompts, resume_conversation, shutdown_conversation (+2 more));外部调用 7 个(start, assert!, assert_eq!, json!, panic!, println!, vec!)。

snapshot_rollback_past_compaction_replays_append_only_history423–507 ↗
async fn snapshot_rollback_past_compaction_replays_append_only_history() -> Result<()>

作用:测试“压缩之后又回滚一轮”的情况。它要确认回滚删掉了该删的后续用户消息,但压缩前的历史和摘要仍然保留给模型看。

数据流:它启动假服务器并准备四次模型响应 → 创建会话 → 发送第一条消息、压缩、再发送一条压缩后的消息 → 提交回滚一轮的操作 → 等到回滚事件 → 再发送回滚后的新消息 → 检查请求数量、摘要提示、旧消息、新消息和被回滚消息是否正确出现或消失 → 最后生成快照用于长期对比。

调用关系:这个测试关注回滚和压缩交界处的行为。它调用 mount_sse_sequence、start_test_conversation、user_turn、compact_conversation,并直接向 conversation 提交 ThreadRollback 操作。

调用图:调用 6 个内部函数(mount_sse_sequence, sse, compact_conversation, network_disabled, start_test_conversation, user_turn);外部调用 8 个(start, assert!, assert_eq!, wait_for_event, assert_snapshot!, panic!, println!, vec!)。

snapshot_rollback_followup_turn_trims_context_updates513–636 ↗
async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()>

作用:测试回滚带有设置变更的一轮后,后续请求不会把这些设置变化重复塞进去。它防止模型上下文里出现重复的开发者指令或工作目录提示。

数据流:它搭好三次假模型响应 → 创建指定模型的会话 → 发送第一轮 → 提交线程设置覆盖,比如工作目录和开发者指令 → 发送第二轮 → 回滚第二轮 → 再发跟进消息 → 检查回滚前后相关设置文本都只出现一次,并确认最后一条用户消息是跟进消息 → 生成快照。

调用关系:这个测试专门覆盖“回滚 + 线程设置更新”的边角场景。它使用 start_test_conversation 和 user_turn 驱动对话,用 submit_thread_settings 注入设置变化,并用快照工具记录请求形状。

调用图:调用 5 个内部函数(mount_sse_sequence, local_selections, network_disabled, start_test_conversation, user_turn);外部调用 10 个(default, start, assert_eq!, submit_thread_settings, wait_for_event, assert_snapshot!, panic!, println!, create_dir_all, vec!)。

normalize_line_endings638–655 ↗
fn normalize_line_endings(value: &mut Value)

作用:递归地把一整份 JSON 里的字符串换行统一成 \n。它让请求快照和断言在不同操作系统上更稳定。

数据流:输入一个可变 JSON 值 → 如果是字符串,就替换其中的 \r\n 或 \r → 如果是数组或对象,就继续处理里面每个子值 → 原地改好整棵 JSON。

调用关系:收集请求正文后会用它做清洗,尤其是在 gather_request_bodies 和第二次压缩测试里。它是测试比较前的“格式整理工”。

gather_requests657–662 ↗
fn gather_requests(request_log: &[ResponseMock]) -> Vec<ResponsesRequest>

作用:把多个 mock 响应对象里记录到的请求合并成一个列表。这样测试不用关心请求分别落在哪个 mock 上。

数据流:输入 ResponseMock 列表 → 遍历每个 mock 的请求记录 → 摊平成一个 ResponsesRequest 列表 → 返回合并后的请求。

调用关系:它被 gather_request_bodies 调用。前者负责拿到原始请求对象,后者再继续把请求正文变成 JSON。

调用图:被 1 处调用(gather_request_bodies);外部调用 1 个(iter)。

gather_request_bodies664–671 ↗
fn gather_request_bodies(request_log: &[ResponseMock]) -> Vec<Value>

作用:收集所有发给模型的请求正文,并转成 JSON,方便后续断言。它还会顺手统一换行符。

数据流:输入多个 ResponseMock → 调用 gather_requests 拿到请求列表 → 对每个请求取 body_json → 对每份 JSON 调 normalize_line_endings → 输出整理后的 JSON 请求列表。

调用关系:compact_resume_and_fork_preserve_model_history_view 用它拿到整个流程中的模型请求。它把“从 mock 里取请求”和“清洗 JSON”打包成一步。

调用图:调用 1 个内部函数(gather_requests);被 1 处调用(compact_resume_and_fork_preserve_model_history_view)。

mount_initial_flow673–726 ↗
async fn mount_initial_flow(server: &MockServer) -> Vec<ResponseMock>

作用:为第一个主测试在假服务器上安排五次模型响应,并给每次响应设置匹配条件。这样每一步请求都会拿到对应的假回复。

数据流:输入 mock 服务器 → 构造五段 SSE 假事件流:首次回复、摘要回复、压缩后回复、恢复后完成、分叉后完成 → 分别按请求正文内容挂载匹配规则 → 返回五个可查询请求记录的 ResponseMock。

调用关系:compact_resume_and_fork_preserve_model_history_view 会先调用它布置舞台。它内部使用 sse 生成事件流,用 mount_sse_once_match 把事件流绑定到符合条件的请求上。

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

mount_second_compact_sequence728–751 ↗
async fn mount_second_compact_sequence(server: &MockServer) -> ResponseMock

作用:为第二次压缩测试按固定顺序准备八次模型响应。它不按复杂条件匹配,而是按请求到来的顺序一一返回。

数据流:输入 mock 服务器 → 构造八段 SSE 假事件流,包括两次摘要回复和多次完成事件 → 调用 mount_sse_sequence 按顺序挂载 → 返回一个记录整串请求的 ResponseMock。

调用关系:compact_resume_after_second_compaction_preserves_history 用它保证请求日志顺序就是实际发生顺序,方便比较第二次压缩和再次恢复之间的历史前缀。

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

start_test_conversation753–771 ↗
async fn start_test_conversation(
    server: &MockServer,
    model: Option<&str>,
) -> (Arc<TempDir>, Config, Arc<ThreadManager>, Arc<CodexThread>)

作用:创建一个连接到假模型服务器的测试会话。它把真实网络地址换成 mock 服务器地址,还设置压缩提示词和可选模型名。

数据流:输入 mock 服务器和可选模型名 → 生成 base_url → 用 test_codex 构建测试配置,改模型提供方、接口地址、压缩提示词和模型名 → 启动测试会话 → 返回临时 home、配置、线程管理器和 CodexThread。

调用关系:四个主要测试都用它开始一段新对话。它是测试里的“开局搭环境”函数,后续 user_turn、compact_conversation、resume_conversation 等都依赖它返回的会话和管理器。

调用图:调用 1 个内部函数(test_codex);被 4 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view, snapshot_rollback_followup_turn_trims_context_updates, snapshot_rollback_past_compaction_replays_append_only_history);外部调用 2 个(pin, format!)。

user_turn773–788 ↗
async fn user_turn(conversation: &Arc<CodexThread>, text: &str)

作用:向会话提交一条用户文字,并等待这一轮完成。测试用它模拟用户真的说了一句话。

数据流:输入会话和文本 → 包成 Op::UserInput 操作 → 提交给 CodexThread → 等待 TurnComplete 事件 → 没有显式返回值;如果提交或等待失败,测试失败。

调用关系:所有主要测试都会反复调用它推进对话。它把底层协议操作包装成一句简单的“用户说话”。

调用图:被 4 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view, snapshot_rollback_followup_turn_trims_context_updates, snapshot_rollback_past_compaction_replays_append_only_history);外部调用 3 个(default, wait_for_event, vec!)。

compact_conversation790–807 ↗
async fn compact_conversation(conversation: &Arc<CodexThread>)

作用:触发一次对话压缩,并确认系统发出了预期警告和完成事件。测试用它模拟用户或系统要求把长历史压成摘要。

数据流:输入会话 → 提交 Op::Compact → 等待 Warning 事件,并检查警告文字等于 COMPACT_WARNING_MESSAGE → 再等待 TurnComplete → 不返回内容,但会推动会话状态变成已压缩。

调用关系:压缩相关测试都通过它执行压缩。它不检查请求内容本身,而是确保压缩流程在事件层面完成,之后测试再去看模型请求里有没有摘要。

调用图:被 3 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view, snapshot_rollback_past_compaction_replays_append_only_history);外部调用 3 个(assert_eq!, wait_for_event, panic!)。

fetch_conversation_path809–811 ↗
fn fetch_conversation_path(conversation: &Arc<CodexThread>) -> std::path::PathBuf

作用:取出当前会话保存到磁盘上的 rollout 文件路径。这个文件就像对话账本,恢复和分叉都要从它开始。

数据流:输入会话 → 调用 rollout_path 获取路径 → 如果没有路径就让测试失败 → 返回 PathBuf。

调用关系:压缩恢复和分叉测试在关掉、恢复或分叉会话前会调用它。resume_conversation 和 fork_thread 后面都需要这个路径。

调用图:被 2 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view)。

shutdown_conversation813–818 ↗
async fn shutdown_conversation(conversation: &Arc<CodexThread>)

作用:关闭当前会话并等待它彻底停下来。这样测试可以模拟程序退出后,再从磁盘记录恢复会话。

数据流:输入会话 → 调用 shutdown_and_wait → 等待关闭完成 → 不返回内容;关闭失败就让测试失败。

调用关系:恢复类测试会在 resume_conversation 前调用它。它确保不是在同一个活跃线程上继续,而是真的走“保存后再打开”的路径。

调用图:被 2 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view)。

resume_conversation820–837 ↗
async fn resume_conversation(
    manager: &ThreadManager,
    config: &Config,
    path: std::path::PathBuf,
) -> Arc<CodexThread>

作用:从 rollout 文件恢复一段已经保存的对话。测试用它确认磁盘里的历史可以重新变成可继续聊天的线程。

数据流:输入线程管理器、配置和 rollout 路径 → 创建一个假的 API key 认证管理器 → 调用 resume_thread_from_rollout → 等待恢复完成 → 返回新的 CodexThread。

调用关系:两个压缩恢复主测试在 shutdown_conversation 之后调用它。它把活儿交给 ThreadManager 的恢复能力,自己只负责准备测试认证和取出恢复后的 thread。

调用图:调用 3 个内部函数(auth_manager_from_auth, resume_thread_from_rollout, from_api_key);被 2 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view);外部调用 2 个(pin, clone)。

fork_thread840–856 ↗
async fn fork_thread(
    manager: &ThreadManager,
    config: &Config,
    path: std::path::PathBuf,
    nth_user_message: usize,
) -> Arc<CodexThread>

作用:从已有 rollout 文件的某个用户消息位置分叉出新会话。测试用它模拟“回到旧对话节点,开一条新分支”。

数据流:输入线程管理器、配置、rollout 路径和第几个用户消息 → 调用 ThreadManager 的 fork_thread → 等待分叉完成 → 返回分叉后的 CodexThread。

调用关系:两个主测试在恢复会话后调用它,然后再用 user_turn 往分支里发送新消息。它是检查“分叉后模型历史是否正确”的关键入口。

调用图:调用 1 个内部函数(fork_thread);被 2 处调用(compact_resume_after_second_compaction_preserves_history, compact_resume_and_fork_preserve_model_history_view);外部调用 2 个(pin, clone)。

待处理轮次连续性

这些测试覆盖进行中的输入重放,以及用于保留对话状态或发出警告的恢复会话初始化细节。

core/tests/suite/pending_input.rs源码 ↗
testtest runtime

这个测试文件把模型服务伪装成一个可控的流式服务器。流式服务器就是一点点吐出事件的假模型,像水龙头一样可以暂停和继续。测试先安排好模型会返回什么,再往 Codex 会话里塞用户输入、转向输入、智能体邮件、睡眠工具调用等,然后检查 Codex 发给模型的第二次、第三次请求里到底带了哪些内容。重点不是测模型聪不聪明,而是测“半路来了新消息怎么办”。比如,等待另一个智能体时新输入应该打断等待;长时间 sleep 应该被新输入中断;模型已经开始推理后,有些输入不能粗暴抢占;自动压缩上下文时,新用户话也不能混进压缩请求。文件里有不少小工具函数,负责造假事件、提交输入、等待指定事件、从请求 JSON 里抽出文本,测试函数则像排练事故现场一样验证每种边界情况。

函数细节28
ev_message_item_done41–51 ↗
fn ev_message_item_done(id: &str, text: &str) -> Value

作用:造一个“助手消息已经完整结束”的假事件,供测试用的流式模型服务器返回。有人用它,是为了模拟模型最终说完了一段话。

数据流:输入一条消息的 id 和文本 → 它把这些拼成符合响应格式的 JSON 事件 → 输出一个 JSON 值,表示 assistant 的 message 已完成。

调用关系:它是测试造数据的小零件。多个测试把它放进流式返回片段里,让 Codex 以为模型真的发完了一条助手消息。

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

sse_event53–55 ↗
fn sse_event(event: Value) -> String

作用:把一个 JSON 事件包装成 SSE 文本。SSE 是 Server-Sent Events,意思是服务器一条条推送事件给客户端的文本格式。

数据流:输入一个 JSON 事件 → 它交给测试支持库按 SSE 格式编码 → 输出一段字符串,假服务器可以直接发出去。

调用关系:它服务于早期几个手写 StreamingSseChunk 的测试,让假模型服务器能按流式接口吐事件。

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

message_input_texts57–69 ↗
fn message_input_texts(body: &Value, role: &str) -> Vec<String>

作用:从一次发给模型的请求 JSON 里,找出某个角色的用户文本片段。测试用它确认新输入有没有被放进正确的请求里。

数据流:输入请求体 JSON 和角色名,比如 user → 它沿着 input 数组筛选 message、指定 role、input_text 内容 → 输出所有匹配到的文本列表,不修改任何东西。

调用关系:它是多个测试的验货工具。测试跑完后从假服务器拿到请求记录,再用它检查第一轮、第二轮或压缩后的请求里是否包含某句 prompt。

调用图:被 6 处调用(any_new_input_interrupts_sleep, injected_user_input_triggers_follow_up_request_with_deltas, steer_interrupts_wait_agent_and_is_sent_in_follow_up_request, steered_user_input_follows_compact_when_only_the_steer_needs_follow_up, steered_user_input_waits_for_model_continuation_after_mid_turn_compact, steered_user_input_waits_when_tool_output_triggers_compact_before_next_request);外部调用 1 个(get)。

function_call_output_text71–81 ↗
fn function_call_output_text(body: &'a Value, call_id: &str) -> Option<&'a str>

作用:从请求 JSON 中取出某个工具调用的输出文本。测试用它确认被打断的 wait 或 sleep 工具有没有把“被新输入中断”这件事告诉下一轮模型。

数据流:输入请求体 JSON 和工具调用 call_id → 它在 input 数组里找 type 为 function_call_output 且 call_id 匹配的项 → 找到就返回 output 字符串,找不到就返回空。

调用关系:它主要被等待智能体和睡眠中断相关测试使用。那些测试先制造工具调用,再在后续请求里用它取出工具结果做断言。

调用图:被 2 处调用(any_new_input_interrupts_sleep, steer_interrupts_wait_agent_and_is_sent_in_follow_up_request);外部调用 1 个(get)。

assert_interrupted_sleep_output83–97 ↗
fn assert_interrupted_sleep_output(output: Option<&str>)

作用:检查 sleep 工具的输出确实表示“睡眠被新输入打断”,并且里面带了合理的耗时数字。

数据流:输入可能存在的输出文本 → 它先确认文本存在,再检查固定前缀和后缀,最后确认中间的 wall time 能解析成数字 → 如果不符合就让测试失败。

调用关系:它被 any_new_input_interrupts_sleep 调用。那个测试关心两次 sleep 都被不同来源的新输入打断,这个函数负责把输出格式查清楚。

调用图:被 1 处调用(any_new_input_interrupts_sleep);外部调用 2 个(assert!, panic!)。

chunk99–104 ↗
fn chunk(event: Value) -> StreamingSseChunk

作用:快速造一个不需要等待开闸的流式返回片段。它让测试代码少写重复模板。

数据流:输入一个 JSON 事件 → 它把事件编码成 SSE 文本,并设置 gate 为空,表示马上可以发送 → 输出一个 StreamingSseChunk。

调用关系:大量测试用它搭建假模型的响应序列。它是最常见的普通事件包装器。

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

gated_chunk106–111 ↗
fn gated_chunk(gate: oneshot::Receiver<()>, events: Vec<Value>) -> StreamingSseChunk

作用:造一个“先卡住,等测试允许后再发”的流式返回片段。gate 可以理解成一道门闩,测试想制造半路插入输入时就会用它。

数据流:输入一个一次性接收器 gate 和一组 JSON 事件 → 它把事件编码成 SSE 文本并挂上 gate → 输出一个必须等信号才会发送的 StreamingSseChunk。

调用关系:需要精确控制时序的测试会用它,比如先让模型开始推理或开始消息,再在完成前塞入新输入,最后打开 gate 继续流程。

调用图:调用 1 个内部函数(sse)。

response_completed_chunks113–118 ↗
fn response_completed_chunks(response_id: &str) -> Vec<StreamingSseChunk>

作用:快速生成一组最简单的模型响应:创建响应,然后立刻完成。测试用它表示后续请求没有复杂内容,只要结束即可。

数据流:输入 response_id → 它生成 response_created 和 completed 两个流式片段 → 输出片段列表。

调用关系:多个测试把它作为第二轮或第三轮假响应,重点留给请求内容检查,而不是模型输出本身。

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

build_codex120–127 ↗
async fn build_codex(server: &StreamingSseServer) -> Arc<CodexThread>

作用:用给定的假流式服务器搭一个测试用 Codex 会话。它把重复的建会话步骤收起来。

数据流:输入 StreamingSseServer → 它创建 test_codex,指定模型名 gpt-5.4,并连接到这个假服务器 → 输出可共享的 CodexThread。

调用关系:队列邮件和推理阶段相关的几个测试用它启动会话。它把测试从搭建细节中解放出来,让测试只写场景。

调用图:调用 1 个内部函数(test_codex);被 3 处调用(queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item, queued_inter_agent_mail_triggers_follow_up_after_reasoning_item, user_input_does_not_preempt_after_reasoning_item)。

submit_user_input129–143 ↗
async fn submit_user_input(codex: &CodexThread, text: &str)

作用:向 Codex 会话提交一条普通用户输入。它模拟用户正式发起或追加一轮对话。

数据流:输入 CodexThread 和文本 → 它把文本包装成 Op::UserInput 操作,其他上下文用默认值 → 异步提交给 Codex,成功后没有额外返回。

调用关系:大多数测试用它开启第一轮 prompt,或模拟正常排队的新用户输入。它把输入交给 CodexThread 的 submit 接口。

调用图:调用 1 个内部函数(submit);被 7 处调用(any_new_input_interrupts_sleep, queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item, queued_inter_agent_mail_triggers_follow_up_after_reasoning_item, steer_interrupts_wait_agent_and_is_sent_in_follow_up_request, steered_user_input_follows_compact_when_only_the_steer_needs_follow_up, steered_user_input_waits_for_model_continuation_after_mid_turn_compact, user_input_does_not_preempt_after_reasoning_item);外部调用 2 个(default, vec!)。

submit_danger_full_access_user_turn145–175 ↗
async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str)

作用:提交一条权限更宽松的用户输入,允许测试真实执行一个会产生大量输出的 shell 命令。这里的“危险”是指沙箱和审批限制被放宽,只用于测试特定场景。

数据流:输入 TestCodex 和文本 → 它计算当前目录对应的沙箱策略和权限配置,设置不需要审批、禁用权限限制、使用本地环境 → 把这些设置连同用户文本一起提交给 Codex。

调用关系:只有工具输出触发压缩的测试会用它。那个测试需要 shell 命令真的跑出大输出,所以不能用默认受限权限。

调用图:调用 2 个内部函数(local_selections, turn_permission_fields);被 1 处调用(steered_user_input_waits_when_tool_output_triggers_compact_before_next_request);外部调用 2 个(default, vec!)。

steer_user_input177–191 ↗
async fn steer_user_input(codex: &CodexThread, text: &str)

作用:向正在进行的会话发送“转向输入”。可以理解成用户在模型还没彻底结束时插话,要求它调整方向。

数据流:输入 CodexThread 和文本 → 它把文本包装成 UserInput,并调用 steer_input,其他参数留空或默认 → Codex 收到这条半路插入的输入。

调用关系:很多测试用它制造关键事故:模型等待、睡眠、推理或压缩期间用户插话。后面的断言会检查这条插话何时进入下一次请求。

调用图:调用 1 个内部函数(steer_input);被 5 处调用(any_new_input_interrupts_sleep, steer_interrupts_wait_agent_and_is_sent_in_follow_up_request, steered_user_input_follows_compact_when_only_the_steer_needs_follow_up, steered_user_input_waits_when_tool_output_triggers_compact_before_next_request, user_input_does_not_preempt_after_reasoning_item);外部调用 2 个(default, vec!)。

submit_queue_only_agent_mail193–214 ↗
async fn submit_queue_only_agent_mail(codex: &CodexThread, text: &str)

作用:提交一封只入队、不主动触发新回合的智能体邮件。这里的智能体邮件就是不同 agent 之间的内部消息。

数据流:输入 CodexThread 和邮件文本 → 它构造从 /root/worker 发给 root 的 InterAgentCommunication,并设置 trigger_turn 为 false → 提交后又发一个 list-voices 操作当作屏障,等到回应出现,说明前面的邮件已处理到队列里。

调用关系:智能体邮件相关测试用它模拟子智能体在主模型工作中途送来新消息。它再借助 wait_for_event 确认邮件已经排好队,测试才继续放开模型响应。

调用图:调用 4 个内部函数(submit, root, try_from, new);被 3 处调用(any_new_input_interrupts_sleep, queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item, queued_inter_agent_mail_triggers_follow_up_after_reasoning_item);外部调用 2 个(new, wait_for_event)。

wait_for_reasoning_item_started216–225 ↗
async fn wait_for_reasoning_item_started(codex: &CodexThread)

作用:等待 Codex 报告“推理项已经开始”。推理项可以理解成模型进入了内部思考阶段的记录。

数据流:输入 CodexThread → 它持续等事件,直到看到 ItemStarted 且里面是 Reasoning → 返回时代表测试已经卡在模型推理开始之后。

调用关系:需要在推理中途塞入输入的测试用它控制时间点。它把等待工作交给通用的 wait_for_event。

调用图:被 2 处调用(queued_inter_agent_mail_triggers_follow_up_after_reasoning_item, user_input_does_not_preempt_after_reasoning_item);外部调用 1 个(wait_for_event)。

wait_for_agent_message227–234 ↗
async fn wait_for_agent_message(codex: &CodexThread, text: &str)

作用:等待 Codex 发出指定文本的助手消息。测试用它确认模型的某段输出确实被系统接收并广播出来。

数据流:输入 CodexThread 和期望文本 → 它等到 AgentMessage 事件,且消息内容等于期望值 → 返回该事件,并用断言保证确实等到了助手消息。

调用关系:多个测试在打开 gate 后用它确认旧任务或新转向任务已跑到预期位置,再继续检查请求历史。

调用图:被 4 处调用(queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item, steered_user_input_follows_compact_when_only_the_steer_needs_follow_up, steered_user_input_waits_for_model_continuation_after_mid_turn_compact, user_input_does_not_preempt_after_reasoning_item);外部调用 2 个(assert!, wait_for_event)。

wait_for_turn_complete236–238 ↗
async fn wait_for_turn_complete(codex: &CodexThread)

作用:等待当前对话回合结束。测试用它确保后台流程都收尾了,再去检查假服务器收到的请求。

数据流:输入 CodexThread → 它等待 TurnComplete 事件 → 事件出现后返回,不直接产出业务数据。

调用关系:几乎所有测试结尾都会用它做同步点。没有这个等待,测试可能太早读取请求列表,造成误判。

调用图:被 8 处调用(any_new_input_interrupts_sleep, queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item, queued_inter_agent_mail_triggers_follow_up_after_reasoning_item, steer_interrupts_wait_agent_and_is_sent_in_follow_up_request, steered_user_input_follows_compact_when_only_the_steer_needs_follow_up, steered_user_input_waits_for_model_continuation_after_mid_turn_compact, steered_user_input_waits_when_tool_output_triggers_compact_before_next_request, user_input_does_not_preempt_after_reasoning_item);外部调用 1 个(wait_for_event)。

wait_for_sleep_item_started240–262 ↗
async fn wait_for_sleep_item_started(codex: &CodexThread, call_id: &str, duration_ms: u64)

作用:等待某个 sleep 工具调用开始,并检查它的 id 和睡眠时长是否正确。sleep 工具就是让系统等待一段时间的工具。

数据流:输入 CodexThread、call_id 和 duration_ms → 它等到对应 Sleep 的 ItemStarted 事件 → 取出 SleepItem 并断言 id、duration_ms 与期望一致。

调用关系:sleep 中断测试用它确认长睡眠真的开始后,才发送新输入去打断。它避免测试在 sleep 尚未启动时就插话。

调用图:被 1 处调用(any_new_input_interrupts_sleep);外部调用 3 个(assert_eq!, wait_for_event, unreachable!)。

wait_for_sleep_item_completed264–286 ↗
async fn wait_for_sleep_item_completed(codex: &CodexThread, call_id: &str, duration_ms: u64)

作用:等待某个 sleep 工具调用结束,并确认结束的是同一个 sleep。这里既包括自然结束,也包括被新输入打断后的完成事件。

数据流:输入 CodexThread、call_id 和 duration_ms → 它等到对应 Sleep 的 ItemCompleted 事件 → 取出项目并断言字段正确。

调用关系:sleep 中断测试在每次插入新输入后调用它,用来确认系统已经把 sleep 收尾并记录完成事件。

调用图:被 1 处调用(any_new_input_interrupts_sleep);外部调用 3 个(assert_eq!, wait_for_event, unreachable!)。

steer_interrupts_wait_agent_and_is_sent_in_follow_up_request289–348 ↗
async fn steer_interrupts_wait_agent_and_is_sent_in_follow_up_request()

作用:测试模型正在调用 wait_agent 等另一个智能体时,用户插话会打断等待,并且插话会出现在下一次发给模型的请求里。

数据流:它先搭一个假服务器,让第一轮返回 wait_agent 工具调用 → 提交初始输入并等到进入等待 → 发送转向输入 → 等回合完成后读取第二次请求,检查里面既有初始输入也有转向输入,还检查 wait_agent 的工具输出写着被新输入打断。

调用关系:这是一个完整场景测试。它使用 start_streaming_sse_server 和 test_codex 搭环境,用 submit_user_input、steer_user_input 驱动会话,再用 message_input_texts 和 function_call_output_text 验证结果。

调用图:调用 7 个内部函数(start_streaming_sse_server, test_codex, function_call_output_text, message_input_texts, steer_user_input, submit_user_input, wait_for_turn_complete);外部调用 4 个(assert_eq!, wait_for_event, from_slice, vec!)。

any_new_input_interrupts_sleep351–456 ↗
async fn any_new_input_interrupts_sleep()

作用:测试任何新输入都会打断正在进行的 sleep:用户转向输入会打断,排队的智能体邮件也会打断。

数据流:它让假模型连续两次发起超长 sleep → 第一次 sleep 开始后发送用户转向输入,确认 sleep 完成并在下一轮请求里记录中断 → 第二次 sleep 开始后提交只排队的智能体邮件,再确认也被中断 → 最后检查请求数量、工具输出和持久化日志里的 SleepItem。

调用关系:这是 sleep 行为的核心测试。它调用 sleep 等待辅助函数、提交用户输入和智能体邮件的辅助函数,还在最后主动 Shutdown 以读取 rollout 文件确认事件被保存。

调用图:调用 11 个内部函数(start_streaming_sse_server, test_codex, assert_interrupted_sleep_output, function_call_output_text, message_input_texts, steer_user_input, submit_queue_only_agent_mail, submit_user_input, wait_for_sleep_item_completed, wait_for_sleep_item_started (+1 more));外部调用 6 个(assert_eq!, wait_for_event, json!, from_slice, read_to_string, vec!)。

assert_two_responses_input_snapshot458–480 ↗
fn assert_two_responses_input_snapshot(snapshot_name: &str, requests: &[Vec<u8>])

作用:把两次发给模型的请求输入部分整理成快照,并和保存的预期结果对比。快照测试就是把复杂输出存成样本,以后跑测试时检查有没有变化。

数据流:输入快照名和请求字节数组 → 它要求必须有两次请求,解析 JSON,取出每个请求的 input 列表,去掉能力说明等容易变的内容 → 生成带标签的文本快照并交给 insta 断言。

调用关系:几个智能体邮件和推理抢占测试都用它做最终验收。它不判断单个字段,而是整体比较两轮输入历史是否和预期完全一致。

调用图:调用 2 个内部函数(default, format_labeled_items_snapshot);被 3 处调用(queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item, queued_inter_agent_mail_triggers_follow_up_after_reasoning_item, user_input_does_not_preempt_after_reasoning_item);外部调用 3 个(assert_eq!, assert_snapshot!, from_slice)。

injected_user_input_triggers_follow_up_request_with_deltas484–587 ↗
async fn injected_user_input_triggers_follow_up_request_with_deltas()

作用:测试模型正在一点点输出文本增量时,如果又提交用户输入,系统会在第一轮结束后发起后续请求,并带上新输入。这个测试目前被标记为忽略,因为它被认为还有不稳定问题。

数据流:它让第一轮响应先发消息增量但把 completed 卡住 → 等到内容增量事件后提交第二个用户 prompt → 放开 completed → 最后检查第一次请求只有 first prompt,第二次请求同时包含 first prompt 和 second prompt。

调用关系:它直接使用 test_codex、start_streaming_sse_server、wait_for_event 和 message_input_texts。虽然被忽略,它仍记录了期望行为,方便未来修复。

调用图:调用 3 个内部函数(start_streaming_sse_server, test_codex, message_input_texts);外部调用 7 个(default, assert!, assert_eq!, wait_for_event, channel, from_slice, vec!)。

queued_inter_agent_mail_triggers_follow_up_after_reasoning_item590–632 ↗
async fn queued_inter_agent_mail_triggers_follow_up_after_reasoning_item()

作用:测试模型已经开始推理后,如果有只排队的智能体邮件到达,系统会在当前模型响应收尾后发起跟进请求,而不是把邮件丢掉。

数据流:它让第一轮先发 reasoning started,再用 gate 卡住 reasoning done 和后续陈旧输出 → 推理开始后提交智能体邮件 → 放开 gate → 等回合完成后检查两次请求的输入快照。

调用关系:它用 build_codex 建会话,用 submit_user_input 开始流程,用 wait_for_reasoning_item_started 找准插入时间,用 submit_queue_only_agent_mail 加入邮件,最后交给 assert_two_responses_input_snapshot 验证历史。

调用图:调用 7 个内部函数(start_streaming_sse_server, assert_two_responses_input_snapshot, build_codex, submit_queue_only_agent_mail, submit_user_input, wait_for_reasoning_item_started, wait_for_turn_complete);外部调用 2 个(channel, vec!)。

queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item635–696 ↗
async fn queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item()

作用:测试模型正在生成 commentary 阶段的助手消息时,队列里的智能体邮件会触发后续请求,并且不会被当前响应后面的陈旧工具调用带偏。

数据流:它让第一轮先开始助手消息,然后卡住消息完成和后续事件 → 消息开始后提交智能体邮件 → 放开 gate,让模型发出 first answer 以及一些应被视为旧流程的内容 → 等完成后用快照检查两次请求输入。

调用关系:它和推理阶段邮件测试类似,只是插入点换成了助手消息阶段。它依赖 build_codex、submit_queue_only_agent_mail、wait_for_agent_message、wait_for_turn_complete 和快照断言。

调用图:调用 7 个内部函数(start_streaming_sse_server, assert_two_responses_input_snapshot, build_codex, submit_queue_only_agent_mail, submit_user_input, wait_for_agent_message, wait_for_turn_complete);外部调用 3 个(wait_for_event, channel, vec!)。

user_input_does_not_preempt_after_reasoning_item699–746 ↗
async fn user_input_does_not_preempt_after_reasoning_item()

作用:测试普通用户转向输入在模型已经开始推理后,不会粗暴抢占当前响应中接下来的工具调用和答案。

数据流:它让第一轮先出现 reasoning started 并暂停 → 此时发送 second prompt → 放开后续事件,让模型继续发工具调用和 first answer → 等完成后用快照确认 second prompt 被安排到正确的后续位置,而不是破坏当前推理后的流程。

调用关系:它和智能体邮件测试形成对照:邮件可以触发某种跟进处理,但用户输入在这个时机不能随便抢占。它使用 build_codex、steer_user_input、wait_for_reasoning_item_started、wait_for_agent_message 和快照检查。

调用图:调用 8 个内部函数(start_streaming_sse_server, assert_two_responses_input_snapshot, build_codex, steer_user_input, submit_user_input, wait_for_agent_message, wait_for_reasoning_item_started, wait_for_turn_complete);外部调用 2 个(channel, vec!)。

steered_user_input_waits_for_model_continuation_after_mid_turn_compact749–839 ↗
async fn steered_user_input_waits_for_model_continuation_after_mid_turn_compact()

作用:测试如果一轮中间触发了自动压缩上下文,用户插话要等模型从压缩后继续完成旧任务,再进入下一次请求。

数据流:它安排四段假响应:初始工具调用、自动压缩摘要、压缩后的旧任务继续、最后处理插话 → 连续提交 first prompt 和 second prompt → 等看到旧任务恢复输出后结束 → 检查压缩后继续请求不包含 second prompt,而最终转向请求包含它。

调用关系:它验证转向输入和自动压缩之间的顺序。它用 submit_user_input 制造排队输入,用 wait_for_agent_message 和 wait_for_turn_complete 等流程稳定,再用 message_input_texts 检查请求体。

调用图:调用 6 个内部函数(start_streaming_sse_server, test_codex, message_input_texts, submit_user_input, wait_for_agent_message, wait_for_turn_complete);外部调用 4 个(assert!, assert_eq!, from_slice, vec!)。

steered_user_input_follows_compact_when_only_the_steer_needs_follow_up842–926 ↗
async fn steered_user_input_follows_compact_when_only_the_steer_needs_follow_up()

作用:测试当模型原本已经回答完,只是因为用户插话需要后续请求时,系统会先做压缩,再直接处理插话,不会多发一个空的恢复请求。

数据流:它让第一轮先完整输出 first answer,但把 completed 卡住 → 用户此时发送 second prompt → 放开 completed 后触发自动压缩 → 接着模型处理 second prompt → 最后检查压缩请求不含 second prompt,而压缩后的跟进请求含有它。

调用关系:它关注“压缩后是否需要恢复旧任务”这个细分情况。它用 gate 精确控制 completed 的时机,并用 message_input_texts 判断 second prompt 是否被放在正确请求里。

调用图:调用 7 个内部函数(start_streaming_sse_server, test_codex, message_input_texts, steer_user_input, submit_user_input, wait_for_agent_message, wait_for_turn_complete);外部调用 5 个(assert!, assert_eq!, channel, from_slice, vec!)。

steered_user_input_waits_when_tool_output_triggers_compact_before_next_request929–1051 ↗
async fn steered_user_input_waits_when_tool_output_triggers_compact_before_next_request()

作用:测试如果工具输出太大,导致下一次请求前必须先压缩,那么用户插话也要等压缩后的旧流程继续完成,之后才发送给模型。

数据流:它先构造一个会输出大量字符的 shell 命令,让工具结果触发压缩 → 在第一轮开始后发送 second prompt → 放开第一轮 completed → 系统依次发压缩请求、压缩后的继续请求、最后的插话请求 → 测试检查 second prompt 不在压缩请求和恢复请求里,只在最终请求里。

调用关系:这是自动压缩和工具输出场景的压力测试。它需要 submit_danger_full_access_user_turn 放宽权限来运行大输出命令,再用 wait_for_turn_complete 等整条链路结束,最后检查三次后续请求的输入内容。

调用图:调用 6 个内部函数(start_streaming_sse_server, test_codex, message_input_texts, steer_user_input, submit_danger_full_access_user_turn, wait_for_turn_complete);外部调用 8 个(assert!, assert_eq!, cfg!, wait_for_event, json!, channel, from_slice, vec!)。

core/tests/suite/resume.rs源码 ↗
testtest run

这个文件像是在给“断点续聊”功能做体检。测试会先启动一个假的服务器,假装它是模型服务;然后创建一次真实的测试会话,让用户发消息,让假服务器返回助手回复、推理过程等事件。接着测试再用保存下来的 rollout 文件恢复会话。rollout 可以理解成“聊天过程的流水账”,里面记着之前发生过什么。恢复后,测试会检查系统是否把这些旧事件整理成初始消息,让前端或调用方一打开会话就能看到完整上下文。文件还特别检查模型切换:如果旧会话用的是一个模型,新恢复的会话改用另一个模型,系统要告诉模型“这里发生过切换”,但不能每一轮都重复塞同样的提示。整体上,这些测试是在防止恢复会话时出现很隐蔽的问题,比如历史消息缺失、推理内容不显示、基础指令被改掉、模型切换提示重复污染上下文。

函数细节5
resume_until_initial_messages27–58 ↗
async fn resume_until_initial_messages(
    builder: &mut TestCodexBuilder,
    server: &MockServer,
    home: Arc<TempDir>,
    rollout_path: PathBuf,
    predicate: impl Fn(&[EventMsg]) -> bool,
) -

作用:这个辅助函数反复尝试恢复同一个会话,直到恢复出来的初始消息符合测试给定的条件。它的作用是等系统把保存的历史事件稳定地整理好,避免测试因为异步写入还没完成而误判失败。

数据流:输入是一套测试构建器、假的服务器、临时 home 目录、rollout 文件路径,以及一个判断函数。它会在最多 2 秒内循环调用恢复会话的方法,每次查看恢复结果里的 initial_messages,也就是“恢复后应该先展示出来的历史消息”。如果这些消息满足判断函数,就把恢复好的 TestCodex 返回;如果一直不满足,就记录最后看到的内容,超时后直接让测试失败并打印出来。

调用关系:它是两个“恢复后应包含历史消息”的测试共用的小工具。resume_includes_initial_messages_from_rollout_events 和 resume_includes_initial_messages_from_reasoning_events 都会先制造一段会话历史,再调用它等待恢复结果符合预期。它内部把真正恢复会话的工作交给 builder.resume。

调用图:调用 1 个内部函数(resume);被 2 处调用(resume_includes_initial_messages_from_reasoning_events, resume_includes_initial_messages_from_rollout_events);外部调用 8 个(clone, from_millis, from_secs, clone, format!, panic!, now, sleep)。

resume_includes_initial_messages_from_rollout_events61–146 ↗
async fn resume_includes_initial_messages_from_rollout_events() -> Result<()>

作用:这个测试确认:普通聊天轮次被保存后,恢复会话时能重新生成正确的初始消息。也就是说,用户说过什么、助手回过什么、这一轮是否完成,都应该在恢复后看得到。

数据流:它先启动假服务器并创建一个测试会话,再让假服务器准备好一串模拟返回:创建响应、助手消息、完成事件。然后它向会话提交一条用户输入,其中还带有 text_elements,也就是文字片段的附加标记。等这一轮完成后,它用 rollout 路径恢复会话,并检查恢复出来的 initial_messages。最后它确认这些消息按顺序包含:轮次开始、用户消息、助手消息、 token 数量、轮次完成;同时确认用户文本、文字标记、助手回复、完成事件里的轮次编号都没有错。

调用关系:这是一个端到端风格的测试:它通过 start_mock_server、mount_sse_once 和 sse 搭出假的模型响应,通过 test_codex 创建测试中的 Codex 会话,通过 codex.submit 触发一轮对话,再借助 resume_until_initial_messages 等恢复结果稳定下来。它验证的是恢复流程里最基础的一条主线。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, resume_until_initial_messages);外部调用 7 个(clone, default, assert_eq!, wait_for_event, panic!, skip_if_no_network!, vec!)。

resume_includes_initial_messages_from_reasoning_events149–237 ↗
async fn resume_includes_initial_messages_from_reasoning_events() -> Result<()>

作用:这个测试确认:如果会话里保存过助手的推理摘要和原始推理内容,恢复会话时这些内容也会出现在初始消息里。它防止“聊天能恢复,但思考过程丢了”的问题。

数据流:它先创建测试会话,并打开 show_raw_agent_reasoning,这表示测试希望看到原始推理内容。假服务器随后返回一串事件:响应创建、推理条目、助手最终消息、响应完成。测试提交用户输入并等轮次完成后,再按 rollout 文件恢复会话。恢复结果出来后,它检查 initial_messages 的顺序和内容:先是轮次开始和用户消息,然后是推理摘要、原始推理文本、助手消息、token 数量、轮次完成。最后它确认推理摘要是“Summarized step”,原始内容是“raw detail”,助手回复和轮次编号也正确。

调用关系:它和普通消息恢复测试很像,但多了 reasoning,也就是“模型思考内容”的场景。它调用 resume_until_initial_messages 来等待恢复结果,并依赖 mount_sse_once、sse、ev_reasoning_item 等测试工具制造带推理事件的假模型响应。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, resume_until_initial_messages);外部调用 7 个(clone, default, assert_eq!, wait_for_event, panic!, skip_if_no_network!, vec!)。

resume_switches_models_preserves_base_instructions240–369 ↗
async fn resume_switches_models_preserves_base_instructions() -> Result<()>

作用:这个测试确认:恢复旧会话时即使换了模型,原来的基础指令也必须保留,同时系统要正确加入一次模型切换提示。它防止新模型接手旧会话时忘记原规则,或者被重复的切换提示干扰。

数据流:它先用模型 gpt-5.2 创建会话,提交一轮用户输入,并从发给假服务器的请求里取出当时的 instructions,也就是给模型的基础说明。随后它准备两个恢复后的假响应,再用另一个模型 gpt-5.3-codex 恢复同一个 rollout。恢复后连续提交两轮用户输入。测试最后检查这两次请求:两次请求里的基础 instructions 都必须和最初完全一样;第一轮恢复后的请求里必须出现至少一个 <model_switch> 提示;第二轮里这个提示只能出现一次,不能因为恢复后继续对话而越积越多。

调用关系:这个测试不走 resume_until_initial_messages,而是直接用 resume_builder.resume 恢复会话,然后观察恢复后发给假服务器的请求内容。它依赖 mount_sse_once 记录初始请求,依赖 mount_sse_sequence 收集恢复后的两次请求,用来验证模型切换提示和基础指令的配合是否正确。

调用图:调用 5 个内部函数(mount_sse_once, mount_sse_sequence, sse, start_mock_server, test_codex);外部调用 7 个(clone, default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

resume_model_switch_is_not_duplicated_after_pre_turn_override372–461 ↗
async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Result<()>

作用:这个测试确认:恢复会话后,如果在下一轮开始前又临时覆盖了模型设置,系统也不能重复加入模型切换提示。它防止“恢复时切一次、临时设置又切一次”导致上下文里出现重复说明。

数据流:它先用模型 gpt-5.2 创建并完成一轮会话,让 rollout 里有可恢复的历史。然后它用 gpt-5.3-codex 恢复这个会话。恢复后,在真正提交下一条用户消息之前,它通过 submit_thread_settings 把线程模型临时改成 gpt-5.4。接着提交一条用户输入并等待完成。最后它查看这次发给假服务器的 developer 消息,也就是开发者级别的上下文提示,统计里面包含 <model_switch> 的文本数量,并断言数量正好是 1。

调用关系:它测试的是一个更细的边界情况:恢复本身会产生模型切换提示,恢复后开聊前的线程设置覆盖也可能产生提示。它通过 start_mock_server、mount_sse_once、test_codex 搭好环境,通过 submit_thread_settings 制造“开聊前覆盖模型”的场景,最后检查请求内容有没有重复。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 7 个(clone, default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

core/tests/suite/resume_warning.rs源码 ↗
testtest run

这个测试像是在模拟一次“接着上次聊天继续”的场景。它先造出一段假的旧聊天记录,里面明确写着旧模型叫 previous-model;然后再准备一份当前配置,里面的新模型叫 current-model。接着测试代码让系统按这段旧记录恢复一个会话。重点不是测试聊天内容,而是看系统启动恢复流程时,能不能发现“旧模型”和“新模型”不一致,并发出一个 Warning 事件,也就是给用户看的警告。文件里的 resume_history 负责搭建这段旧历史记录,emits_warning_when_resumed_model_differs 负责跑完整个测试流程,并等待警告出现。最后它还短暂等待一下,避免后台任务没收尾就影响别的测试。

函数细节2
resume_history22–80 ↗
fn resume_history(
    config: &codex_core::config::Config,
    previous_model: &str,
    rollout_path: &std::path::Path,
) -> InitialHistory

作用:这个函数用来伪造一段“从旧对话恢复”的历史记录。它把旧模型名、当前配置和一个记录文件路径塞进历史数据里,让后面的测试可以像真的恢复会话一样运行。

数据流:进去的是当前配置、旧模型名字、以及旧聊天记录文件的位置 → 它创建一个固定的回合编号,把工作目录、权限设置、沙箱设置、模型名、推理摘要等信息组装成一段回合上下文,再把“回合开始、用户发 seed、上下文、回合结束”这些项目按顺序放进历史列表 → 出来的是一个 InitialHistory::Resumed,也就是“这是一次恢复旧会话”的初始历史对象。

调用关系:它是测试的布景工具,由 emits_warning_when_resumed_model_differs 调用。它不负责判断模型是否一致,只负责把一段带有 previous-model 的旧记录准备好,交给真正的恢复线程流程去处理。

调用图:调用 1 个内部函数(default);被 1 处调用(emits_warning_when_resumed_model_differs);外部调用 4 个(to_path_buf, legacy_sandbox_policy, Resumed, vec!)。

emits_warning_when_resumed_model_differs83–135 ↗
async fn emits_warning_when_resumed_model_differs()

作用:这个测试函数验证一个关键行为:恢复旧会话时,如果旧模型和当前模型不同,系统必须发出警告。它保证用户不会在不知情的情况下用另一个模型继续旧对话。

数据流:开始时它创建临时目录,加载一份测试配置,并把当前模型设为 current-model → 然后创建一个空的 rollout 文件,调用 resume_history 生成一段旧模型为 previous-model 的恢复历史 → 接着准备测试用的线程管理器和认证信息,让系统恢复这个会话 → 最后它等待会话里出现 Warning 事件,并检查警告文字同时包含 previous-model 和 current-model;测试结束前再睡一小会儿,给后台任务一点收尾时间。

调用关系:这是整个文件的主测试,由测试框架自动运行。它调用 resume_history 准备旧历史,也调用测试辅助工具创建线程管理器、认证管理器和默认配置;恢复会话后,它通过 wait_for_event 等待系统发出的 Warning 事件,以此判断恢复流程是否做对了。

调用图:调用 4 个内部函数(auth_manager_from_auth, thread_manager_with_models_provider, resume_history, from_api_key);外部调用 8 个(from_millis, new, assert!, load_default_config_for_test, wait_for_event, panic!, write, sleep)。

分叉与窗口谱系

这些文件验证持久化历史如何支持线程分叉,以及请求窗口标识符如何在压缩、恢复和分叉过程中演变。

core/tests/suite/fork_thread.rs源码 ↗
testtest run

这是一组自动化测试,检查 Codex 的“对话分叉”功能。可以把一段对话想成一条聊天时间线,分叉就是从中间某个点另开一条新线。文件先用假的服务器模拟模型接口返回结果,这样测试不用真的依赖外部模型行为。第一个测试连续发三条用户消息,然后从记录文件里找出每次用户输入的位置,验证第一次分叉会删掉后面的消息,第二次再分叉会继续往前删,只留下更早的历史。第二个测试验证另一种情况:即使没有原始记录文件路径,只要手里有已经保存下来的历史内容,也能开出新线程。辅助函数 read_rollout_items 负责读取“rollout 文件”,也就是按行保存的对话记录,并过滤掉会话元信息,只留下真正要比较的历史条目。

函数细节3
fork_thread_twice_drops_to_first_message25–149 ↗
async fn fork_thread_twice_drops_to_first_message()

作用:这个测试确认:同一段对话连续分叉两次时,历史会一步步被裁到更早的位置,而不是残留后面的用户消息。它保护的是“回退到旧对话点”这个体验不能出错。

数据流:进去的是一个假的模型服务器、三条用户输入文本,以及当前对话写出的 rollout 记录文件路径。测试先让假服务器准备好三次响应,再把“first”“second”“third”三条消息依次送进 Codex,并等待每一轮完成。之后它读取原始记录,找出用户消息在记录里的位置,算出第一次分叉后应该保留哪些记录;然后真的调用分叉功能,读取新线程记录并比较。接着它再从第一次分叉出来的线程继续分叉一次,重新计算应该剩下的记录,最后确认第二个新线程的文件内容正好符合预期。

调用关系:这是本文件最核心的回归测试之一。它会用 test_codex 搭出测试用的 Codex 实例,用 wiremock 的假服务器接住模型请求,用 sse 生成模拟的流式返回,并多次调用 read_rollout_items 把磁盘上的历史记录读出来做比较。它主要是在验证 thread_manager.fork_thread 对 ForkSnapshot::TruncateBeforeNthUserMessage 这种裁剪方式的行为。

调用图:调用 3 个内部函数(sse, test_codex, read_rollout_items);外部调用 11 个(default, given, start, new, TruncateBeforeNthUserMessage, wait_for_event, assert_eq!, skip_if_no_network!, vec!, method (+1 more))。

fork_thread_from_history_does_not_require_source_rollout_path152–222 ↗
async fn fork_thread_from_history_does_not_require_source_rollout_path()

作用:这个测试确认:从一份已经拿到手的历史记录创建分叉时,不一定非要知道原来的记录文件在哪里。这样在历史来自数据库、内存或别的来源时,分叉功能也能工作。

数据流:进去的是一个假的模型服务器、一条用户输入,以及从原线程 rollout 文件中读出来的历史条目。测试先启动假服务器并创建一段对话,发送“fork me from stored history”这条消息,等这一轮完成。然后它读取源线程的历史内容,但在构造 InitialHistory::Resumed 时故意把 rollout_path 设成 None。接着调用 fork_thread_from_history 创建新线程,再读取新线程的记录文件。最后它把源历史和新线程历史都转成 JSON 值,检查新线程的历史开头确实包含原来提供的那份历史。

调用关系:这个测试覆盖的是另一条分叉入口:不是从某个文件路径直接分叉,而是从传入的 InitialHistory 历史数据分叉。它同样依赖 test_codex 建测试环境、wiremock 模拟响应、sse 生成假流式事件,并通过 read_rollout_items 读取源线程和新线程的记录来比较。

调用图:调用 3 个内部函数(sse, test_codex, read_rollout_items);外部调用 11 个(default, given, start, new, assert!, wait_for_event, Resumed, skip_if_no_network!, vec!, method (+1 more))。

read_rollout_items224–242 ↗
fn read_rollout_items(path: &std::path::Path) -> Vec<RolloutItem>

作用:这个小工具函数把 rollout 记录文件读成一组可比较的历史条目。它会跳过空行和会话元信息,只保留真正的对话记录,方便测试判断分叉后的历史对不对。

数据流:进去的是一个文件路径。函数先把整个文件读成文本,再逐行处理:空行直接跳过;非空行先按 JSON 解析成通用值,再转成 RolloutLine 这种记录行结构。每一行里如果是 SessionMeta 这类会话说明信息,就不放进结果;如果是普通 RolloutItem,就加入列表。出来的是一个 RolloutItem 数组,供测试拿来和预期历史比较。读取失败或 JSON 格式不对时,它会直接让测试失败,并给出包含文件路径或出错行内容的提示。

调用关系:这是两个测试共用的读文件助手。fork_thread_twice_drops_to_first_message 用它反复读取原线程和分叉线程的记录,计算和核对裁剪结果;fork_thread_from_history_does_not_require_source_rollout_path 用它读取源历史和新线程历史,确认传入的历史被保留下来。

调用图:被 2 处调用(fork_thread_from_history_does_not_require_source_rollout_path, fork_thread_twice_drops_to_first_message);外部调用 5 个(new, format!, from_str, from_value, read_to_string)。

core/tests/suite/window_headers.rs源码 ↗
testtest run

这个测试文件关心的是 x-codex-window-id 这个请求头。可以把它理解成每次发给模型时贴在信封上的“对话窗口标签”,格式大概是“线程编号:第几代窗口”。测试先启动一个假的模型服务器,让它按顺序返回预设回复;然后创建一个 Codex 对话线程,依次发送普通用户消息、触发 compact(压缩上下文,把长对话整理成摘要)、再发送消息、关闭、恢复会话,最后从旧记录 fork(分叉出新线程)。它最后查看假的服务器收到的 5 次请求,确认规则没乱:压缩请求本身还用第 0 代,压缩后的新请求变成第 1 代;恢复会话后仍然沿用原来的线程编号和第 1 代;但 fork 出来的新线程要换一个线程编号,并从第 0 代重新开始。

函数细节5
window_id_advances_after_compact_persists_on_resume_and_resets_on_fork22–100 ↗
async fn window_id_advances_after_compact_persists_on_resume_and_resets_on_fork() -> Result<()>

作用:这是主测试,验证“窗口编号”在压缩、恢复、分叉这三种场景下是否正确。它模拟一整段真实对话流程,然后检查每次模型请求头里的编号。

数据流:进去的是一套测试环境:假的模型服务器、预设的模型返回内容、一个可配置的 Codex 测试实例。它先发普通消息,再发压缩命令,再发普通消息,然后关闭并恢复会话,最后 fork 出新线程继续发消息。出来的是一组断言结果:如果请求次数不是 5 次,或者请求头里的线程编号、窗口代数不符合预期,测试就失败;如果全部匹配,就返回成功。

调用关系:它是这个文件的总导演。它调用 start_mock_server 和 mount_sse_sequence 搭好假服务器,调用 test_codex 创建被测系统,靠 submit_user_turn、submit_compact_turn、shutdown_thread 推动对话流程,最后用 window_id_parts 拆开每个请求头并做比较。

调用图:调用 7 个内部函数(mount_sse_sequence, start_mock_server, test_codex, shutdown_thread, submit_compact_turn, submit_user_turn, window_id_parts);外部调用 5 个(clone, assert_eq!, assert_ne!, skip_if_no_network!, vec!)。

submit_user_turn102–117 ↗
async fn submit_user_turn(codex: &Arc<CodexThread>, text: &str) -> Result<()>

作用:这个辅助函数负责向 Codex 线程发送一条普通用户消息,并等到这一轮对话结束。它让主测试不用反复写同样的“发送消息、等待完成”的步骤。

数据流:进去的是一个 CodexThread(正在运行的对话线程)和一段用户文字。它把文字包装成 UserInput,再提交成 Op::UserInput 操作,然后一直等到收到 TurnComplete(这一轮处理完毕)的事件。出来的是成功或失败结果;成功时说明这条用户消息已经完整走完,线程状态也已经向前推进。

调用关系:它只被主测试 window_id_advances_after_compact_persists_on_resume_and_resets_on_fork 使用。主测试在压缩前、压缩后、恢复后、分叉后都调用它,用来制造会触发模型请求的普通对话轮次。

调用图:被 1 处调用(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork);外部调用 3 个(default, wait_for_event, vec!)。

submit_compact_turn119–128 ↗
async fn submit_compact_turn(codex: &Arc<CodexThread>) -> Result<()>

作用:这个辅助函数负责触发一次 compact,也就是把已有对话压缩成摘要,以便后续请求用新的上下文窗口。它还检查系统是否发出了预期的压缩警告。

数据流:进去的是一个 CodexThread。它向线程提交 Op::Compact,然后等待 Warning(警告)事件,并确认警告文字正好等于 COMPACT_WARNING_MESSAGE;接着继续等待 TurnComplete,表示压缩这一轮已经结束。出来的是成功或失败结果;如果没有警告、警告内容不对,或者压缩没有完成,测试会失败。

调用关系:它被主测试在第一次普通消息之后调用。它制造“窗口代数应该发生变化”的关键场景:压缩本身仍是旧窗口,压缩完成后的下一次普通请求才应该进入新一代窗口。

调用图:被 1 处调用(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork);外部调用 3 个(assert_eq!, wait_for_event, panic!)。

shutdown_thread130–134 ↗
async fn shutdown_thread(codex: &Arc<CodexThread>) -> Result<()>

作用:这个辅助函数负责优雅关闭一个 Codex 对话线程,并等到系统确认关闭完成。它确保后面的恢复会话或分叉操作是在干净状态下进行的。

数据流:进去的是一个 CodexThread。它提交 Op::Shutdown 关闭命令,然后等待 ShutdownComplete(关闭完成)事件。出来的是成功或失败结果;成功时说明线程已经停好,不再有未完成的后台处理。

调用关系:它被主测试多次调用:先关闭初始线程,好测试 resume(恢复会话);再关闭恢复后的线程,好继续 fork(分叉);最后关闭分叉线程,收尾整个测试流程。

调用图:被 1 处调用(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork);外部调用 1 个(wait_for_event)。

window_id_parts136–147 ↗
fn window_id_parts(request: &ResponsesRequest) -> (String, u64)

作用:这个小工具函数从一次模型请求里取出 x-codex-window-id 请求头,并拆成“线程编号”和“窗口代数”。主测试靠它把请求头变成容易比较的数据。

数据流:进去的是一条 ResponsesRequest,也就是假服务器记录下来的模型请求。它读取请求头 x-codex-window-id,用最后一个冒号把它拆成前面的 thread_id 和后面的 generation,再把 generation 转成数字。出来的是一个二元组:线程编号字符串和窗口代数数字;如果请求头缺失、格式不含冒号,或代数不是整数,它会直接让测试失败。

调用关系:它只被主测试在收集完 5 次请求后使用。主测试把每条请求交给它拆解,然后比较 compact、resume、fork 前后的编号变化是否符合规则。

调用图:调用 1 个内部函数(header);被 1 处调用(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork)。

持久化 rollout 内容

这些套件检查哪些运行时变更和消息载荷会或不会写入由 rollout 支持的会话历史。

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

这个测试文件盯的是“图片输入有没有被正确存档”。这里的 rollout 可以理解成一份逐行写入的聊天流水账,每行是一个 JSON 记录。文件模拟两种常见操作:一种是复制粘贴本地图片文件,另一种是拖拽一张已经带有 data URL 的图片。测试会启动一个假的服务端,让 Codex 以为模型正常回复了,然后提交带图片和文字的用户输入。等这一轮对话结束并关闭后,它读取 rollout 文件,从里面找出带图片的用户消息,再和预期格式逐项对比。特别重要的是,本地图片会被包在一组“图片开始标签、图片内容、图片结束标签”之间,而拖拽图片则直接作为图片内容保存。这样可以保证真实用户操作留下来的记录稳定、可重放,也不会因为图片来源不同而悄悄变形。

函数细节6
find_user_message_with_image31–53 ↗
fn find_user_message_with_image(text: &str) -> Option<ResponseItem>

作用:这个函数从 rollout 文本里找出“用户发的、并且包含图片”的那条消息。它像在聊天记录里翻页,只挑出真正带图片的用户发言。

数据流:进去的是整份 rollout 文本 → 它按行查看,每行先去掉空白,再尝试当作 JSON 解析成 rollout 记录;解析失败的行会跳过 → 如果某一行是用户消息,而且内容里有图片项,就返回这条消息;如果全找不到,就返回空。

调用关系:两个测试在读完 rollout 文件后都会调用它,用它把大量流水账缩小到最关键的一条用户图片消息。它自己把每行交给 serde_json::from_str 解析,后面的测试再拿返回结果继续检查图片地址和完整结构。

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

extract_image_url55–63 ↗
fn extract_image_url(item: &ResponseItem) -> Option<String>

作用:这个函数从一条消息里取出图片的地址。测试需要它拿到实际保存下来的图片地址,再放进预期结果里做整体比较。

数据流:进去的是一条 ResponseItem 消息 → 它只看消息内容列表,寻找第一个图片内容项 → 找到就把 image_url 复制出来返回;如果这不是消息,或者消息里没有图片,就返回空。

调用关系:两个测试在找到带图片的用户消息后都会调用它。因为有些图片地址可能由系统加工或转存后生成,测试先取出实际地址,再用这个地址组装预期消息,避免把测试重点放错到地址细节上。

调用图:被 2 处调用(copy_paste_local_image_persists_rollout_request_shape, drag_drop_image_persists_rollout_request_shape)。

read_rollout_text65–77 ↗
async fn read_rollout_text(path: &Path) -> anyhow::Result<String>

作用:这个函数负责稳妥地读取 rollout 文件内容。它会等一小会儿,避免测试刚结束时文件还没来得及写完,导致误判失败。

数据流:进去的是 rollout 文件路径 → 它最多尝试 50 次:如果文件存在、能读出来、并且不是空白,就立刻返回文本;每次没成功就睡 20 毫秒 → 如果一直没读到有效内容,最后再读一次,并在失败时附上更清楚的错误说明。

调用关系:两个测试在 Codex 关闭后都会调用它来拿到最终记录。它依赖文件是否存在、读取文件、短暂 sleep 这些外部操作,是测试和磁盘文件之间的缓冲层,让异步写文件的时机不至于影响测试稳定性。

调用图:被 2 处调用(copy_paste_local_image_persists_rollout_request_shape, drag_drop_image_persists_rollout_request_shape);外部调用 4 个(from_millis, exists, read_to_string, sleep)。

write_test_png79–86 ↗
fn write_test_png(path: &Path, color: [u8; 4]) -> anyhow::Result<()>

作用:这个函数造一张很小的 PNG 测试图片并写到指定路径。复制粘贴本地图片的测试需要一个真实存在的图片文件,不能只凭一个假路径。

数据流:进去的是目标路径和一个 RGBA 颜色值;RGBA 就是红、绿、蓝和透明度四个通道 → 它先创建父目录,再生成一张 2×2 像素、全是这个颜色的小图片 → 最后把图片保存到磁盘,成功后返回空结果。

调用关系:只有本地图片复制粘贴测试会调用它。它把测试环境准备好,让后续 UserInput::LocalImage 指向一个真实图片文件;内部借助 image 库创建像素图,并用文件系统创建目录。

调用图:被 1 处调用(copy_paste_local_image_persists_rollout_request_shape);外部调用 4 个(from_pixel, parent, Rgba, create_dir_all)。

copy_paste_local_image_persists_rollout_request_shape89–187 ↗
async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Result<()>

作用:这个测试检查:用户复制粘贴一张本地图片时,rollout 里保存的请求格式是否符合预期。重点是本地图片应该带有系统生成的“图片开始/结束”文字标记,并使用默认图片清晰度设置。

数据流:开始时它先在测试目录写入一张 PNG 图片,并启动假服务端准备好一次模型回复 → 然后创建测试用 Codex 会话,提交一轮用户输入:先是一张本地图片,再是一段文字“pasted image” → 等对话完成、关闭 Codex 后,它读取 rollout 文件,找出带图片的用户消息,取出实际图片地址,再组装预期消息并断言两者完全相等。

调用关系:这是本文件的一个主测试流程。它会调用 start_mock_server、test_codex、mount_sse_once 等测试工具搭好假环境,调用 write_test_png 准备图片,调用 read_rollout_text 读取记录,再调用 find_user_message_with_image 和 extract_image_url 做检查。它验证的是“本地图片输入”这条链路从提交到落盘没有走样。

调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, extract_image_url, find_user_message_with_image, read_rollout_text, write_test_png);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

drag_drop_image_persists_rollout_request_shape190–278 ↗
async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>

作用:这个测试检查:用户拖拽图片,也就是直接提交一个图片 data URL 时,rollout 里保存的请求格式是否正确。它确保这种图片不会被错误地包上本地文件专用的文字标签。

数据流:开始时它准备一个内嵌在字符串里的 PNG data URL,并启动假服务端返回一次正常回复 → 然后创建测试 Codex 会话,提交一轮用户输入:先是这张图片,再是一段文字“dropped image” → 等对话完成并关闭后,它读取 rollout,找出带图片的用户消息,取出实际图片地址,构造预期消息,最后比较实际和预期是否完全一致。

调用关系:这是另一个主测试流程,和本地图片测试形成对照。它同样借助 start_mock_server、test_codex、mount_sse_once 搭环境,借助 read_rollout_text、find_user_message_with_image、extract_image_url 检查结果。不同点是它不需要写本地图片文件,验证的是“已编码图片输入”这条链路。

调用图:调用 9 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, extract_image_url, find_user_message_with_image, read_rollout_text);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

core/tests/suite/model_overrides.rs源码 ↗
testtest

这个测试文件关心的是一个很具体但很重要的边界:线程设置覆盖项,也就是“只对当前这次对话生效的临时设置”。它先搭一个假的服务端,启动一个测试用的 Codex 会话,然后提交一份临时设置,比如把模型改成 o3、推理强度改成 High 或 Medium。接着它正常关闭会话,等系统发出“关闭完成”的事件。最后检查磁盘上的 config.toml。第一条测试确认:如果一开始就有配置文件,临时设置不会把里面的 model = "gpt-4o" 改掉。第二条测试确认:如果一开始没有配置文件,临时设置也不会新建一个。可以把它理解成试衣间里的临时换装:你可以临时穿另一件衣服照镜子,但不能自动把你衣柜里的衣服替换掉。

函数细节2
thread_settings_update_does_not_persist_when_config_exists12–45 ↗
async fn thread_settings_update_does_not_persist_when_config_exists()

作用:这个测试确认:当用户家目录里已经有 config.toml 时,本次对话里的临时模型设置不会写回这个文件。也就是说,临时改模型只影响当前会话,不会破坏用户原来的长期配置。

数据流:开始时,它启动一个模拟服务端,并在测试用的 home 目录里写入一份 config.toml,内容是 model = "gpt-4o"。然后它创建测试会话,提交一份临时线程设置,把模型改成 o3,并把推理强度设成 High。之后它请求关闭 Codex,等待关闭完成事件,再重新读取 config.toml。最后它比较文件内容,确认仍然和最初写入的一模一样,没有被临时设置改掉。

调用关系:这是一条独立的异步测试,由测试框架运行。它先借助 start_mock_server 准备假的后端服务,再用 test_codex 创建测试中的 Codex 会话;中途通过 submit_thread_settings 把临时设置送进去;最后用 wait_for_event 等系统真正关完,再用 read_to_string 读文件,并用 assert_eq! 判断结果是否符合预期。

调用图:调用 2 个内部函数(start_mock_server, test_codex);外部调用 5 个(default, assert_eq!, submit_thread_settings, wait_for_event, read_to_string)。

thread_settings_update_does_not_create_config_file48–77 ↗
async fn thread_settings_update_does_not_create_config_file()

作用:这个测试确认:如果一开始没有 config.toml,提交临时模型设置也不会创建这个文件。它防止系统把“临时选择”误当成“保存为默认设置”。

数据流:开始时,它启动模拟服务端,并创建一个没有 config.toml 的测试会话。它先检查配置文件确实不存在。然后它提交一份临时线程设置,把模型改成 o3,并把推理强度设成 Medium。接着它请求关闭 Codex,等待关闭完成事件。最后它再次检查 config.toml 的路径,确认这个文件仍然不存在。

调用关系:这条测试同样由异步测试框架运行,流程和上一条相似,但重点从“不要改已有文件”换成“不要创建新文件”。它使用 start_mock_server 和 test_codex 搭环境,用 submit_thread_settings 送入临时设置,用 wait_for_event 确认关闭流程结束,再用 assert! 检查文件是否不存在。

调用图:调用 2 个内部函数(start_mock_server, test_codex);外部调用 4 个(default, assert!, submit_thread_settings, wait_for_event)。

core/tests/suite/override_updates.rs源码 ↗
testtest run

这里测试的是一个很容易出错的边界情况:用户或前端先改了权限、工作目录、协作说明等线程设置,但还没有开始新一轮对话。正常情况下,这些改动只是“准备状态”,不应该立刻写成 rollout 文件。rollout 可以理解成系统保存的一份会话轨迹或记录单;如果过早生成,就像客人还没点菜,餐厅已经把订单打出来了,会造成历史记录不准。每个测试都会启动一个假的服务器,创建一个测试用的 Codex 实例,提交一种设置覆盖,然后马上关机。最后它检查 rollout 文件根本不存在。三个测试分别覆盖权限策略、运行环境、协作模式,确保这些不同入口都不会提前落盘。

函数细节4
collab_mode_with_instructions17–26 ↗
fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode

作用:这个小工具函数用来快速造出一个“协作模式”设置,并可选地塞入一段开发者说明。测试里需要构造这种设置时,用它可以少写重复代码。

数据流:进去的是一段可有可无的说明文字;函数把它放进 CollaborationMode 结构里,同时固定使用默认模式、指定模型名,并把没有提供的推理强度留空;出来的是一个完整的协作模式对象,供测试提交给系统。

调用关系:它不是单独运行的测试,而是给 thread_settings_update_without_user_turn_does_not_record_collaboration_update 打下手。协作模式测试需要一份带说明文字的设置,于是先调用它,再把结果交给线程设置更新流程。

调用图:被 1 处调用(thread_settings_update_without_user_turn_does_not_record_collaboration_update)。

thread_settings_update_without_user_turn_does_not_record_permissions_update29–58 ↗
async fn thread_settings_update_without_user_turn_does_not_record_permissions_update() -> Result<()>

作用:这个测试确认:只更新审批权限,但没有新的用户发言时,系统不会生成 rollout 记录。审批权限就是决定系统做某些事前要不要问用户同意的规则。

数据流:测试先在有网络时继续运行,启动一个假的服务器,再创建一个 Codex 测试实例,并把初始审批策略设成“按需询问”。接着它提交一个线程设置覆盖,把审批策略改成“永不询问”。然后它让 Codex 关机,等待关机完成事件。最后它查看预期的 rollout 文件路径,确认文件不存在;也就是说,这次权限改动没有被提前写成会话记录。

调用关系:这个测试由 Tokio 异步测试框架启动。它先借助 start_mock_server 和 test_codex 搭好假的运行环境,再通过 submit_thread_settings 把权限覆盖发给 Codex,之后用 wait_for_event 等待 ShutdownComplete。整个故事的重点是:设置更新发生了,但因为没有用户新一轮输入,所以最终不应该留下 rollout 文件。

调用图:调用 2 个内部函数(start_mock_server, test_codex);外部调用 5 个(default, assert!, submit_thread_settings, wait_for_event, skip_if_no_network!)。

thread_settings_update_without_user_turn_does_not_record_environment_update61–88 ↗
async fn thread_settings_update_without_user_turn_does_not_record_environment_update() -> Result<()>

作用:这个测试确认:只更新运行环境,比如工作目录,但没有新的用户发言时,也不会生成 rollout 记录。运行环境可以理解成系统接下来要在哪个文件夹、用哪些本地选择来工作。

数据流:测试先跳过不适合联网的环境,然后启动假的服务器并创建 Codex 测试实例。它新建一个临时目录,把这个目录包装成本地环境选择,作为线程设置覆盖提交进去。随后它关闭 Codex,等到关机完成。最后检查 rollout 文件路径,确认文件没有出现;结果说明,仅仅换了环境,还不足以写入会话轨迹。

调用关系:这个测试同样是异步测试框架自动运行的。它用 start_mock_server 和 test_codex 准备环境,用 TempDir 创建安全的临时目录,再通过 local_selections 把目录变成系统认识的环境设置,最后交给 submit_thread_settings。关机后由 wait_for_event 确认流程结束,再做文件不存在的断言。

调用图:调用 3 个内部函数(start_mock_server, local_selections, test_codex);外部调用 6 个(default, new, assert!, submit_thread_settings, wait_for_event, skip_if_no_network!)。

thread_settings_update_without_user_turn_does_not_record_collaboration_update91–119 ↗
async fn thread_settings_update_without_user_turn_does_not_record_collaboration_update() -> Result<()>

作用:这个测试确认:只更新协作模式里的说明文字,但没有新的用户发言时,不会生成 rollout 记录。协作模式里的说明文字可以理解成给模型的额外工作要求。

数据流:测试在网络条件允许时启动假的服务器,创建 Codex 测试实例,并准备一段“覆盖用的协作说明”。它调用 collab_mode_with_instructions 把这段文字做成完整的协作模式设置,然后把它作为线程设置覆盖提交。接着测试关闭 Codex,等待关机完成。最后它检查 rollout 文件是否存在,预期是不存在,说明这段协作说明没有在用户开口前被提前保存。

调用关系:这个测试是三类覆盖测试中的协作模式版本。它自己负责搭环境和收尾,把构造协作模式的细活交给 collab_mode_with_instructions,把提交设置交给 submit_thread_settings,把关机等待交给 wait_for_event。它和另外两个测试一起守住同一条规则:没有新的用户回合,就不要记录这些设置更新。

调用图:调用 3 个内部函数(start_mock_server, test_codex, collab_mode_with_instructions);外部调用 5 个(default, assert!, submit_thread_settings, wait_for_event, skip_if_no_network!)。

会话发现与状态存储

这些测试验证如何查找已存储会话,并从 SQLite 支持的元数据和 rollout 文件中重建线程状态。

core/tests/suite/rollout_list_find.rs源码 ↗
testtest

系统会把每次会话保存成一个 jsonl 文件,一行一条记录。如果用户以后想继续某个会话,程序必须先找到对应文件。这个测试文件就像给“找聊天记录”这个功能做体检。它先在临时文件夹里造出假的会话文件,文件里只写最关键的 session_meta,也就是“这是谁的会话”的身份信息。然后测试查找函数能不能按会话 ID 找到它;即使父目录被 .gitignore 忽略,也不能漏掉;如果 SQLite(一种本地小数据库)里有更明确的位置,就优先用数据库;如果数据库没有匹配项,就回到硬盘上搜索。它还用真正的 RolloutRecorder 写一次文件,确认真实写出的记录也能按名字找到。最后,它也检查 archived_sessions 里的归档会话能被单独找到。

函数细节11
write_minimal_rollout_with_id_in_subdir25–33 ↗
fn write_minimal_rollout_with_id_in_subdir(codex_home: &Path, subdir: &str, id: Uuid) -> PathBuf

作用:在指定子目录里造一个最小可用的会话记录文件,里面带着给定的会话 ID。测试用它来快速准备“应该能被找到”的假数据。

数据流:输入是 codex_home 根目录、子目录名和一个 UUID(一串唯一编号)→ 它拼出类似 子目录/2024/01/01 的日期路径,创建这些文件夹,再拼出带 ID 的 rollout 文件名 → 它调用 write_minimal_rollout_with_id_at_path 往文件里写内容,最后返回这个文件的完整路径。

调用关系:它是测试数据工厂。write_minimal_rollout_with_id 用它创建普通 sessions 里的记录;find_archived_locates_rollout_file_by_id 用它创建 archived_sessions 里的归档记录。真正写文件内容的活儿交给 write_minimal_rollout_with_id_at_path。

调用图:调用 1 个内部函数(write_minimal_rollout_with_id_at_path);被 2 处调用(find_archived_locates_rollout_file_by_id, write_minimal_rollout_with_id);外部调用 3 个(join, format!, create_dir_all)。

write_minimal_rollout_with_id_at_path35–55 ↗
fn write_minimal_rollout_with_id_at_path(file: &Path, id: Uuid)

作用:把一个最小的 rollout 文件写到指定位置。这个文件不模拟完整聊天,只写足够让查找功能认出会话 ID 的第一行。

数据流:输入是目标文件路径和会话 UUID → 它创建这个文件,并写入一行 JSON;这行 JSON 的类型是 session_meta,里面放入 id、时间、当前目录、来源、版本、模型提供方等基本字段 → 输出没有单独返回值,但硬盘上多了一个可被搜索到的 jsonl 文件。

调用关系:它是所有“手工造 rollout 文件”测试的底层写入工具。write_minimal_rollout_with_id_in_subdir 会调用它;find_prefers_sqlite_path_by_id 也直接调用它,在数据库指向的位置造一个文件,用来测试数据库路径是否会被优先采用。

调用图:被 2 处调用(find_prefers_sqlite_path_by_id, write_minimal_rollout_with_id_in_subdir);外部调用 2 个(create, writeln!)。

write_minimal_rollout_with_id59–61 ↗
fn write_minimal_rollout_with_id(codex_home: &Path, id: Uuid) -> PathBuf

作用:在默认的 sessions 目录里创建一个带指定 ID 的最小会话文件。大多数测试不关心子目录名,所以用这个简化版。

数据流:输入是 codex_home 根目录和 UUID → 它固定选择 sessions 作为子目录,并把工作转交给 write_minimal_rollout_with_id_in_subdir → 输出是刚创建的 rollout 文件完整路径。

调用关系:它是普通会话测试最常用的准备步骤。按 ID 查找、忽略 gitignore、数据库回退、数据库优先等测试都会先用它放一个文件,再调用 find_thread_path_by_id_str 去验证系统能找到。

调用图:调用 1 个内部函数(write_minimal_rollout_with_id_in_subdir);被 5 处调用(find_falls_back_to_filesystem_when_sqlite_has_no_match, find_handles_gitignore_covering_codex_home_directory, find_ignores_granular_gitignore_rules, find_locates_rollout_file_by_id, find_prefers_sqlite_path_by_id)。

upsert_thread_metadata63–85 ↗
async fn upsert_thread_metadata(
    codex_home: &Path,
    thread_id: ThreadId,
    rollout_path: PathBuf,
) -> StateDbHandle

作用:往测试用的状态数据库里写入一条“某个会话 ID 对应某个 rollout 文件路径”的元数据。这里的 upsert 意思是“有就更新,没有就插入”。

数据流:输入是 codex_home、ThreadId 和 rollout 文件路径 → 它初始化 StateRuntime,也就是测试用的本地状态数据库运行对象;标记回填完成;用 ThreadMetadataBuilder 组装会话元数据,并把当前目录设成 codex_home → 最后把元数据写入数据库,返回这个数据库句柄,供后续查找函数使用。

调用关系:它专门服务于涉及 SQLite 状态库的测试。find_prefers_sqlite_path_by_id 用它制造“数据库知道正确路径”的情况;find_falls_back_to_filesystem_when_sqlite_has_no_match 用它制造“数据库只有无关记录”的情况。

调用图:调用 2 个内部函数(new, init);被 2 处调用(find_falls_back_to_filesystem_when_sqlite_has_no_match, find_prefers_sqlite_path_by_id);外部调用 3 个(to_path_buf, now, default)。

find_locates_rollout_file_by_id88–99 ↗
async fn find_locates_rollout_file_by_id()

作用:确认最基本的场景:只要 sessions 目录里有带某个 ID 的 rollout 文件,按这个 ID 就能找到它。

数据流:测试开始时创建一个临时 home 目录和一个随机 UUID → 用 write_minimal_rollout_with_id 写出一个会话文件 → 调用 find_thread_path_by_id_str 按字符串形式的 ID 查找 → 最后断言找到的路径正好等于刚才写出的路径。

调用关系:这是按 ID 查找功能的基础验收测试。它先让 write_minimal_rollout_with_id 准备文件,再把问题交给核心查找函数 find_thread_path_by_id_str,最后用断言确认结果没有跑偏。

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

find_handles_gitignore_covering_codex_home_directory102–116 ↗
async fn find_handles_gitignore_covering_codex_home_directory()

作用:确认即使项目里的 .gitignore 把整个 .codex 目录忽略了,系统仍然能找到 .codex 里的会话记录。这里的 .gitignore 是 Git 用来声明“不跟踪哪些文件”的规则文件。

数据流:测试先创建一个临时仓库目录,再在里面建 .codex 目录,并写入一条 .gitignore 规则:忽略 .codex 下所有内容 → 然后在 .codex 里写一个带 ID 的 rollout 文件 → 调用 find_thread_path_by_id_str 查找 → 输出结果应是那个文件路径,而不是因为 gitignore 就找不到。

调用关系:它验证查找逻辑不会被外层 Git 忽略规则误伤。准备文件仍然交给 write_minimal_rollout_with_id,真正被检查的是 find_thread_path_by_id_str 在这种目录环境下的表现。

调用图:调用 1 个内部函数(write_minimal_rollout_with_id);外部调用 6 个(new, new_v4, assert_eq!, find_thread_path_by_id_str, create_dir_all, write)。

find_prefers_sqlite_path_by_id119–136 ↗
async fn find_prefers_sqlite_path_by_id()

作用:确认当状态数据库里已经记录了某个会话的路径时,查找结果优先采用数据库里的路径,而不是硬盘扫描时碰到的另一个同 ID 文件。

数据流:输入来自测试内部准备:一个临时 home、一个随机 ID、一个转换后的 ThreadId → 它先在未来日期目录下写一个数据库要指向的 rollout 文件,又在普通 sessions 里写另一个同 ID 文件 → 通过 upsert_thread_metadata 把数据库记录指向未来日期那个文件 → 调用 find_thread_path_by_id_str 并传入数据库句柄 → 结果应该是数据库里的 db_path。

调用关系:这个测试模拟“数据库索引比文件扫描更权威”的情况。它调用 write_minimal_rollout_with_id_at_path 和 write_minimal_rollout_with_id 造出两个候选文件,再通过 upsert_thread_metadata 建数据库索引,最后检查 find_thread_path_by_id_str 是否先听数据库的。

调用图:调用 4 个内部函数(upsert_thread_metadata, write_minimal_rollout_with_id, write_minimal_rollout_with_id_at_path, from_string);外部调用 6 个(new, new_v4, assert_eq!, find_thread_path_by_id_str, format!, create_dir_all)。

find_falls_back_to_filesystem_when_sqlite_has_no_match139–155 ↗
async fn find_falls_back_to_filesystem_when_sqlite_has_no_match()

作用:确认数据库里没有目标会话时,系统不会直接放弃,而是会继续去文件系统里找。文件系统就是硬盘上的文件夹和文件。

数据流:测试创建一个目标 ID,并在 sessions 里写出对应 rollout 文件 → 又创建一个无关 ID,把这个无关会话写入状态数据库 → 调用 find_thread_path_by_id_str,传入含有无关记录的数据库句柄 → 因为数据库没命中目标 ID,函数应回退到扫描文件夹,并返回目标文件路径。

调用关系:它和 find_prefers_sqlite_path_by_id 是一组互补测试:一个看数据库命中时是否优先,一个看数据库没命中时是否还能查硬盘。它依赖 upsert_thread_metadata 制造无关数据库记录,依赖 write_minimal_rollout_with_id 制造真正应该被找到的文件。

调用图:调用 3 个内部函数(upsert_thread_metadata, write_minimal_rollout_with_id, from_string);外部调用 4 个(new, new_v4, assert_eq!, find_thread_path_by_id_str)。

find_ignores_granular_gitignore_rules158–170 ↗
async fn find_ignores_granular_gitignore_rules()

作用:确认 sessions 目录内部如果有更细的 .gitignore 规则,比如忽略所有 jsonl 文件,查找功能也不会因此漏掉会话记录。

数据流:测试创建临时 home 和随机 ID → 先写出一个正常的 rollout 文件 → 再在 sessions/.gitignore 里写入 *.jsonl,表示 Git 会忽略这些 jsonl 文件 → 调用 find_thread_path_by_id_str 查找该 ID → 结果仍应返回那个 rollout 文件路径。

调用关系:它继续检查 gitignore 对查找逻辑的影响,但这次是更细粒度的目录内规则。write_minimal_rollout_with_id 负责造记录,find_thread_path_by_id_str 负责证明查找时不会照搬 Git 的忽略规则。

调用图:调用 1 个内部函数(write_minimal_rollout_with_id);外部调用 5 个(new, new_v4, assert_eq!, find_thread_path_by_id_str, write)。

find_locates_rollout_file_written_by_recorder173–221 ↗
async fn find_locates_rollout_file_written_by_recorder() -> std::io::Result<()>

作用:确认系统不只会找到测试手写的假文件,也能找到真实 RolloutRecorder 写出来的会话文件,并且能按会话名字查到。

数据流:测试先创建临时 home,并用 ConfigBuilder 生成配置 → 创建一个新的 ThreadId 和名字 named thread → 用 RolloutRecorder 写出真实 rollout 文件,调用 persist 和 flush 确保内容落盘 → 再手写 session_index.jsonl,把线程 ID、线程名和更新时间放进去 → 调用 find_thread_meta_by_name_str 按名字查找 → 结果应包含存在的文件路径和 session_meta,且其中的 ID 与原 thread_id 一致,文件内容也包含该 ID;最后关闭 recorder。

调用关系:这是更接近真实运行的一条测试链。它不用最小假文件,而是调用 RolloutRecorder 走真实写入流程;随后 find_thread_meta_by_name_str 读取名字索引并定位文件。最后 recorder.shutdown 做收尾,避免后台写入资源悬着。

调用图:调用 4 个内部函数(default, new, new, new);外部调用 9 个(new, new, assert!, assert_eq!, find_thread_meta_by_name_str, default, format!, read_to_string, write)。

find_archived_locates_rollout_file_by_id224–238 ↗
async fn find_archived_locates_rollout_file_by_id()

作用:确认归档会话也能按 ID 找到。归档会话放在 archived_sessions 目录,和普通 sessions 分开。

数据流:测试创建临时 home 和随机 UUID → 用 write_minimal_rollout_with_id_in_subdir 在 archived_sessions/2024/01/01 下写一个 rollout 文件 → 调用 find_archived_thread_path_by_id_str 查找这个 ID → 结果应返回刚才创建的归档文件路径。

调用关系:它是普通按 ID 查找测试的归档版本。准备工作由 write_minimal_rollout_with_id_in_subdir 完成,检查对象换成 find_archived_thread_path_by_id_str,确保归档目录有自己的查找通道。

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

core/tests/suite/sqlite_state.rs源码 ↗
testtest run

这里测试的是 SQLite 状态库。SQLite 可以理解成一个放在本机文件里的小数据库,用来记录线程会话的摘要信息。Codex 还有 rollout 文件,也就是一行一条 JSON 的会话流水账。这个测试文件会启动假的服务器,模拟模型返回内容,再让 Codex 新建会话、继续旧会话、调用网页搜索、调用 MCP 工具、写日志。它重点确认几件事:新会话不是一创建就入库,而是用户真正发第一条消息后才记录;已有 rollout 文件能被补录进数据库;恢复会话时动态工具不会丢;如果用了网页或外部工具,线程的记忆模式会被标成 polluted,意思是“混入了外部上下文,不适合继续当干净记忆用”;工具调用日志也必须带上线程 ID,方便以后追查。

函数细节9
new_thread_is_recorded_in_state_db56–109 ↗
async fn new_thread_is_recorded_in_state_db() -> Result<()>

作用:检查一个新线程什么时候会写进 SQLite 状态库。它确认 Codex 不会在空会话刚创建时就留下记录,而是在用户发出第一条消息、rollout 文件真正落地后才登记。

数据流:测试先启动一个假的模型服务器,并创建一个启用了 SQLite 功能的 Codex 实例。它读取线程 ID、rollout 文件路径和数据库路径,先确认 rollout 文件不存在、数据库里也查不到这个线程。然后它提交一条用户消息,反复等待数据库更新,最后确认数据库里出现了同一个线程 ID,并且 rollout 文件也已经生成。

调用关系:这是状态库最基础的生命周期测试。它通过 start_mock_server 和 test_codex 搭好测试环境,用 state_db_path 找到数据库,再直接查询 Codex 暴露出来的 state_db,验证“第一次用户输入”这个节点会触发线程持久化。

调用图:调用 2 个内部函数(start_mock_server, test_codex);外部调用 6 个(from_millis, assert!, assert_eq!, state_db_path, try_exists, sleep)。

resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled112–219 ↗
async fn resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled() -> Result<()>

作用:检查恢复旧会话时,之前登记的动态工具还能不能带回来。动态工具就是运行时临时提供给模型的工具说明,如果恢复时丢了,模型后续就不知道这些工具存在。

数据流:测试先让假服务器准备两轮模型响应。然后创建一个带命名空间和函数的动态工具,启动带 SQLite 的线程,并发一条消息让会话写入 rollout。接着用同一个 home 目录和 rollout 路径恢复会话,再提交新消息。最后它查看第二次发给假服务器的请求体,确认里面的 tools 字段包含之前那套动态工具说明。

调用关系:它覆盖“保存 rollout → 按 rollout 恢复 → 发起下一轮请求”这条链路。测试把工具交给 start_thread_with_tools,恢复时交给 resume,最后通过 mock.requests 观察 Codex 发给模型服务器的真实请求,确认恢复逻辑没有漏掉工具元数据。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 6 个(default, assert_eq!, wait_for_event, json!, Namespace, vec!)。

resume_restores_legacy_dynamic_tools_from_rollout_with_sqlite_enabled222–339 ↗
async fn resume_restores_legacy_dynamic_tools_from_rollout_with_sqlite_enabled() -> Result<()>

作用:检查旧格式的动态工具记录也能在恢复会话时被识别。这样即使用户有老版本生成的 rollout 文件,升级后也不至于恢复失败或工具消失。

数据流:测试先正常创建并保存一个会话,然后关掉线程,直接读写 rollout 文件,把第一行会话元数据里的 dynamic_tools 改成旧格式。之后它用修改后的 rollout 恢复会话并提交新消息。最后检查发给假模型服务器的请求,确认旧格式工具被转换成新请求需要的 namespace/function 结构。

调用关系:这个测试位于兼容性链路上。它先借助 test_codex 生成真实 rollout,再用文件读写手动伪造老数据,最后通过 resume 触发恢复逻辑,确保新代码仍能理解旧会话文件。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 9 个(default, new, assert_eq!, wait_for_event, format!, read_to_string, write, json!, vec!)。

backfill_scans_existing_rollouts342–443 ↗
async fn backfill_scans_existing_rollouts() -> Result<()>

作用:检查 Codex 启动 SQLite 状态库时,会不会扫描已经存在的 rollout 文件并补录旧线程。没有这个能力,用户升级到 SQLite 状态库后,过去的会话可能在新索引里找不到。

数据流:测试先生成一个固定线程 ID 和 rollout 相对路径,在 Codex 启动前就把一个包含会话元数据和用户消息的 JSONL rollout 文件写到 home 目录里。然后启用 SQLite 并启动 Codex,等待数据库出现。最后查询状态库,确认这个旧线程被补录进库,路径正确,模型提供方有默认值,并且第一条用户消息被识别出来。

调用关系:它测试的是启动时的“回填”流程。with_pre_build_hook 在 Codex 构建前偷偷放入旧文件,builder.build 触发真实初始化,随后 state_db 查询结果证明后台扫描 rollout 的逻辑工作正常。

调用图:调用 3 个内部函数(start_mock_server, test_codex, from_string);外部调用 8 个(from_millis, now_v7, assert!, assert_eq!, state_db_path, format!, try_exists, sleep)。

user_messages_persist_in_state_db446–496 ↗
async fn user_messages_persist_in_state_db() -> Result<()>

作用:检查用户发出的消息会被记录进状态库的线程摘要里。这里重点不是保存完整聊天,而是确认数据库能记住第一条用户消息,方便列表页或历史页展示。

数据流:测试启动假服务器并准备两次模型完成响应,然后创建启用 SQLite 的 Codex。它等待数据库文件出现,连续提交两条用户消息。之后查询当前线程的元数据,反复等待直到 first_user_message 字段有值,最后确认这个字段确实被写入。

调用关系:它接在基础入库测试之后,关注线程元数据中的用户消息摘要。submit_turn 驱动真实对话流程,state_db.get_thread 读取最终结果,用来验证消息持久化路径没有断。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 6 个(from_millis, assert!, state_db_path, try_exists, sleep, vec!)。

web_search_marks_thread_memory_mode_polluted_when_configured499–535 ↗
async fn web_search_marks_thread_memory_mode_polluted_when_configured() -> Result<()>

作用:检查普通网页搜索发生后,线程会不会被标记为 polluted。这里的 polluted 可以理解成“这段对话混入了外部网页内容”,如果配置要求外部上下文禁用记忆,就必须打上这个标记。

数据流:测试让假模型服务器返回一次网页搜索完成事件,并把配置里的 disable_on_external_context 打开。创建启用 SQLite 的 Codex 后,它提交一条“搜索网页”的消息。随后反复查询数据库中的 thread memory mode,直到看到值变成 polluted,并断言结果正确。

调用关系:它验证模型侧网页搜索事件到状态库标记之间的连接。mount_sse_sequence 模拟搜索事件,submit_turn 触发处理,get_thread_memory_mode 用来观察状态库是否被正确更新。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 4 个(from_millis, assert_eq!, sleep, vec!)。

standalone_web_search_marks_thread_memory_mode_polluted_when_configured538–613 ↗
async fn standalone_web_search_marks_thread_memory_mode_polluted_when_configured() -> Result<()>

作用:检查独立网页搜索扩展被调用时,也会把线程记忆状态标成 polluted。独立网页搜索是 Codex 自己作为工具去访问搜索接口,不完全依赖模型直接返回的网页搜索事件。

数据流:测试先在可联网环境下运行,给假服务器挂上搜索接口响应和两轮模型事件。它创建假 API Key,安装网页搜索扩展,打开 SQLite、StandaloneWebSearch、实时搜索模式,并启用“外部上下文禁用记忆”。提交搜索请求后,它不断查询数据库,直到当前线程的 memory mode 变成 polluted。

调用关系:它覆盖扩展工具调用这条路径。auth_manager_from_auth 和 install_web_search_extension 搭好搜索扩展,模型事件触发 web.run 工具,工具拿到搜索结果后,状态库必须收到“这条线程已被外部信息污染”的更新。

调用图:调用 5 个内部函数(auth_manager_from_auth, mount_sse_sequence, start_mock_server, test_codex, from_api_key);外部调用 13 个(new, from_millis, new, given, new, assert_eq!, install, json!, skip_if_no_network!, sleep (+3 more))。

mcp_call_marks_thread_memory_mode_polluted_when_configured616–745 ↗
async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result<()>

作用:检查调用 MCP 工具后,线程记忆状态会不会被标成 polluted。MCP 是一种让 Codex 连接外部工具服务器的协议,外部工具结果也属于外部上下文。

数据流:测试先准备假模型事件,让模型要求调用一个 MCP echo 工具,并准备第二轮完成响应。它配置一个通过标准输入输出启动的 MCP 测试服务器,打开 SQLite 和“外部上下文禁用记忆”。等 MCP 服务器就绪后,测试提交用户输入,并带上只读权限、无需审批等本轮设置。它等待 MCP 工具调用结束和整轮完成,再查询数据库确认 memory mode 是 polluted。

调用关系:这是外部工具链路的测试。wait_for_mcp_server 确认工具服务器已接上,turn_permission_fields 和 local_selections 提供本轮执行权限,wait_for_event 观察工具调用事件,最后 state_db 证明 MCP 调用会影响线程记忆状态。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, read_only);外部调用 11 个(default, from_millis, assert_eq!, stdio_server_bin, wait_for_event, wait_for_event_match, wait_for_mcp_server, format!, skip_if_no_network!, sleep (+1 more))。

tool_call_logs_include_thread_id748–823 ↗
async fn tool_call_logs_include_thread_id() -> Result<()>

作用:检查工具调用日志写进 SQLite 日志表时,会不会带上线程 ID。这样以后排查问题时,才能知道某条工具调用日志属于哪一个会话。

数据流:测试先模拟一次 shell_command 工具调用,并创建启用 SQLite 的 Codex。提交用户消息后,它启动 log_db 日志层,把一个带 thread_id 字段的 tracing span 写入日志。flush 后,它反复查询数据库日志,找到包含 ToolCall 的记录,并确认这条记录的 thread_id 等于当前线程 ID,消息内容也确实是工具调用日志。

调用关系:它测试的是日志系统和状态库的结合点。codex_state::log_db::start 把 tracing 日志接到数据库,tracing span 提供线程 ID,上层查询 query_logs 再把结果读出来,确保日志落库时没有丢失归属信息。

调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, start);外部调用 11 个(default, from_millis, assert!, assert_eq!, json!, to_string, sleep, new, with_default, registry (+1 more))。