Codex 系统手册

插件、扩展、技能、MCP 和工具测试

stage-23.6.350 个文件

这一阶段不是系统开机或干活的主流程,而是一大块“质检台”。它用假目录、假市场、假服务器和临时仓库,反复检查插件能不能安全发现、安装、加载、分享和推荐;扩展接口能不能按顺序接入;技能会不会被正确找到、隐藏、点名调用;MCP 这种外部工具服务器能不能按配置连接、授权和过滤;最后还核对各种工具说明转换成接口格式时不丢字段、不乱改。它们合在一起,防止外接能力这条链路被改坏。

本阶段的文件50

插件测试基础

这些文件建立共享测试夹具,并验证底层插件加载、存储、提供者和市场基础能力,供更高层插件流程构建。

core-plugins/src/test_support.rs源码 ↗
testtest setup / config load

插件功能要测试时,不能总依赖真实的线上市场或真实用户配置,否则测试会慢、会不稳定。这个文件就是为测试准备一套可控的假环境:先把指定内容写进临时目录,再生成一个看起来像真实精选插件的目录,里面有插件说明、技能文件、MCP 服务配置和应用连接器配置。它还可以生成 OpenAI 精选市场的 marketplace.json,告诉系统“这里有哪些插件、插件从哪里来”。另外,它能写入一个假的插件 SHA 值,用来模拟缓存版本。最后,load_plugins_config 会按测试方式加载配置,并从配置里读出插件功能开关。可以把它理解成测试里的“布景师”:在主角登场前,把舞台、道具和说明牌都摆好。

函数细节8
write_file17–20 ↗
fn write_file(path: &Path, contents: &str)

作用:把一段文字写到指定文件里,并且会先自动创建父目录。测试里用它来省掉反复写“先建目录再写文件”的样板代码。

数据流:输入是一个文件路径和要写入的文字。它先找到这个文件所在的文件夹,把文件夹创建好;然后把文字写进文件。结果是磁盘上多了这个文件,或者原文件内容被替换。

调用关系:它是这个测试工具箱里最底层的写文件小帮手。write_curated_plugin、write_curated_marketplace 和 write_curated_plugin_sha_with 都会把具体内容准备好,再交给它真正落到磁盘上。

调用图:被 3 处调用(write_curated_marketplace, write_curated_plugin, write_curated_plugin_sha_with);外部调用 3 个(parent, create_dir_all, write)。

write_curated_plugin22–58 ↗
fn write_curated_plugin(root: &Path, plugin_name: &str)

作用:创建一个假的“精选插件”目录。这个插件带有基本说明、一个技能文件、一个 MCP 服务配置和一个应用连接器配置,用来让测试看到完整插件长什么样。

数据流:输入是测试根目录和插件名。它会在 root/plugins/插件名 下面写出几类文件:plugin.json 说明插件是谁,SKILL.md 模拟技能,.mcp.json 模拟外部工具服务,.app.json 模拟应用连接器。结果是测试目录里出现一个可被插件系统读取的假插件。

调用关系:它通常不是单独被测试直接关心,而是由 write_curated_marketplace 调用。市场清单写好后,还需要每个插件真的有文件存在,于是这个函数负责把插件实体补齐。

调用图:调用 1 个内部函数(write_file);被 1 处调用(write_curated_marketplace);外部调用 2 个(join, format!)。

write_openai_curated_marketplace60–68 ↗
fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str])

作用:生成一个普通的 OpenAI 精选插件市场清单。测试可以用它模拟“系统自带的精选插件列表”。

数据流:输入是测试根目录和一组插件名。它把这些信息交给通用的 write_curated_marketplace,并指定清单文件名为 marketplace.json、市场名为普通 OpenAI 精选市场名。结果是磁盘上出现对应市场清单和插件目录。

调用关系:它是 write_curated_marketplace 的一个方便入口。调用者不用记具体文件名和市场常量,只要说要哪些插件,它就负责按普通精选市场的格式生成。

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

write_openai_api_curated_marketplace70–78 ↗
fn write_openai_api_curated_marketplace(root: &Path, plugin_names: &[&str])

作用:生成一个 API 场景使用的 OpenAI 精选插件市场清单。它和普通市场类似,但文件名和展示名称不同,用来测试直接走 API 鉴权时的插件回退和缓存刷新行为。

数据流:输入是测试根目录和插件名列表。它调用通用市场写入函数,指定 api_marketplace.json、API 精选市场名,并额外写入展示名 OpenAI Curated。结果是测试环境里有一份 API 专用的精选插件市场。

调用关系:它是 API 精选市场测试的快捷入口。相关测试会调用它搭好市场文件,然后后续插件加载或缓存刷新逻辑再读取这些文件,验证系统能找到正确插件。

调用图:调用 1 个内部函数(write_curated_marketplace);被 2 处调用(returns_api_curated_fallback_plugins_for_direct_provider_auth, refresh_curated_plugin_cache_reinstalls_missing_api_curated_plugin)。

write_curated_marketplace80–126 ↗
fn write_curated_marketplace(
    root: &Path,
    manifest_name: &str,
    marketplace_name: &str,
    display_name: Option<&str>,
    plugin_names: &[&str],
)

作用:按给定市场名和插件列表,写出一份完整的插件市场清单,并顺手创建清单里提到的插件。它是生成测试市场的核心函数。

数据流:输入是根目录、清单文件名、市场名、可选展示名和插件名列表。它先把每个插件名拼成 JSON 片段,说明插件来自本地路径;如果有展示名,就加上界面展示信息;然后把完整市场 JSON 写到 root/.agents/plugins/清单文件名。最后,它逐个调用 write_curated_plugin,把清单里列出的插件目录也创建出来。

调用关系:write_openai_curated_marketplace 和 write_openai_api_curated_marketplace 都把具体参数交给它。它再向下调用 write_file 写市场文件,调用 write_curated_plugin 创建每个插件,是这组测试造数函数的中间枢纽。

调用图:调用 2 个内部函数(write_curated_plugin, write_file);被 2 处调用(write_openai_api_curated_marketplace, write_openai_curated_marketplace);外部调用 2 个(join, format!)。

write_curated_plugin_sha_with128–130 ↗
fn write_curated_plugin_sha_with(codex_home: &Path, sha: &str)

作用:写入一个假的精选插件 SHA 值。SHA 可以理解成一串版本指纹,测试用它来模拟“当前缓存对应哪个插件版本”。

数据流:输入是 Codex 主目录和一段 SHA 字符串。它把这段字符串加上换行,写到 .tmp/plugins.sha。结果是测试环境里出现一个缓存版本标记文件。

调用关系:它使用 write_file 完成实际写入。测试在需要模拟插件缓存已存在、缓存版本变化或缓存校验时,会先用它放好这个版本指纹。

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

load_plugins_config132–156 ↗
async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> PluginsConfigInput

作用:按测试方式加载插件配置,并整理成插件系统需要的输入对象。它让测试不用自己理解完整配置加载流程,也能拿到一份真实形态的配置。

数据流:输入是 Codex 主目录和当前工作目录。它先把这两个路径确认成绝对路径,也就是从磁盘根位置开始的完整路径;然后调用配置加载器读取各层配置,但关闭测试里不需要的受管配置;接着取出最终生效的配置,读取 plugins 和 remote_plugin 两个功能开关;最后创建 PluginsConfigInput,里面带着配置层信息、开关状态和固定的后端 API 地址。

调用关系:它位于测试准备阶段,通常在测试需要真正跑插件配置逻辑前调用。它把底层配置加载工作交给 load_config_layers_state,把功能开关判断交给 feature_enabled,最后组装出插件模块能直接使用的输入。

调用图:调用 5 个内部函数(load_config_layers_state, without_managed_config_for_tests, new, feature_enabled, try_from);外部调用 1 个(as_path)。

feature_enabled158–165 ↗
fn feature_enabled(config: &Value, key: &str, default_enabled: bool) -> bool

作用:从配置里读取某个功能开关是否开启。如果配置没有写这个开关,就使用调用者给的默认值。

数据流:输入是一份 TOML 配置值、功能名和默认开关值。它先找配置里的 features 表,再找指定功能名,确认它是不是布尔值,也就是 true 或 false。找到就返回配置值;找不到或格式不对,就返回默认值。

调用关系:它是 load_plugins_config 里的小判断器。load_plugins_config 用它分别判断 plugins 和 remote_plugin 是否启用,从而决定测试里的插件功能应该按什么状态运行。

调用图:被 1 处调用(load_plugins_config);外部调用 1 个(get)。

plugin/src/provider_tests.rs源码 ↗
testtest run

插件清单可以写很多资源位置,比如技能目录、MCP 服务器配置、应用配置、钩子文件、图标和截图。这个测试文件要确认一件很重要的事:当插件来自某个执行环境时,这些普通文件路径不能只是裸路径,而要被包成“这个环境里的这个文件”。这就像给每个包裹贴上仓库编号,后面系统才知道该去哪个地方取。文件里还测试了安全边界:资源必须在插件根目录里面,不能偷偷指向外面的文件。否则一个插件可能读到不该读的东西。两个小辅助函数分别用来制造测试用的绝对路径,以及制造“环境资源定位器”。两个测试则分别验证正常路径会全部绑定到环境,异常路径会被拒绝并返回明确错误。

函数细节4
absolute11–13 ↗
fn absolute(path: impl AsRef<std::path::Path>) -> AbsolutePathBuf

作用:这个小工具把测试里拼出来的路径变成系统认可的“绝对路径”。测试用它来保证后面的逻辑拿到的是完整路径,而不是相对路径。

数据流:进去的是一个路径对象 → 它先把路径按普通路径引用出来,再检查这个路径确实是绝对路径 → 出来的是一个 AbsolutePathBuf;如果测试传了非绝对路径,它会直接报错,让测试失败。

调用关系:两个测试都会先用 absolute 准备插件根目录和资源文件路径。它把路径准备好后,再交给 ResolvedPlugin::from_environment 去验证和包装。

调用图:调用 1 个内部函数(from_absolute_path_checked);被 2 处调用(environment_descriptor_binds_every_manifest_resource, environment_descriptor_rejects_resources_outside_package_root);外部调用 1 个(as_ref)。

resource15–20 ↗
fn resource(environment_id: &str, path: AbsolutePathBuf) -> PluginResourceLocator

作用:这个小工具用来造一个“某个环境里的某个资源”的期望值。测试用它来和真正解析出来的结果做对比。

数据流:进去的是环境编号和一个绝对路径 → 它把环境编号转成字符串,并和路径一起放进 PluginResourceLocator::Environment → 出来的是一个带环境来源的资源定位对象。

调用关系:environment_descriptor_binds_every_manifest_resource 用它生成期望答案,再用 assert_eq! 和 ResolvedPlugin::from_environment 产生的实际结果比较,确认每个资源都贴上了正确的环境编号。

environment_descriptor_binds_every_manifest_resource23–89 ↗
fn environment_descriptor_binds_every_manifest_resource()

作用:这个测试确认:插件清单里所有能指向文件的地方,都会被正确改成“来自 executor-1 这个环境的资源”。这样后续系统不会误以为这些只是本机普通路径。

数据流:进去没有外部参数,测试自己从当前目录拼出一个插件根目录和多个资源路径 → 它构造一份带技能、MCP 配置、应用配置、钩子、图标、Logo、截图的 PluginManifest → 调用 ResolvedPlugin::from_environment 解析这份清单 → 最后检查解析后的 manifest_path 和 manifest 里每个资源都变成了带 executor-1 环境编号的 PluginResourceLocator。

调用关系:这是正常场景测试。它先借助 absolute 造绝对路径,再调用 ResolvedPlugin::from_environment 做真正的绑定工作,最后用 resource 造出期望结果并用 assert_eq! 校验。

调用图:调用 3 个内部函数(default, from_environment, absolute);外部调用 5 个(new, assert_eq!, Paths, current_dir, vec!)。

environment_descriptor_rejects_resources_outside_package_root92–126 ↗
fn environment_descriptor_rejects_resources_outside_package_root()

作用:这个测试确认:如果插件清单把资源指到了插件根目录外面,系统会拒绝它。这样可以防止插件越界读取或引用不属于自己的文件。

数据流:进去没有外部参数,测试自己准备一个插件根目录和一个位于外部目录的 .mcp.json 路径 → 它构造一份把 mcp_servers 指到外部路径的 PluginManifest → 调用 ResolvedPlugin::from_environment,并期待它失败 → 出来的是一个 ResourceOutsideRoot 错误,里面明确说明根目录是什么、越界路径是什么。

调用关系:这是异常场景测试。它用 absolute 准备根目录和越界路径,把清单交给 ResolvedPlugin::from_environment;这个函数应该发现问题并返回错误,测试再用 assert_eq! 确认错误类型和内容完全正确。

调用图:调用 2 个内部函数(from_environment, absolute);外部调用 3 个(new, assert_eq!, current_dir)。

core-plugins/src/provider_tests.rs源码 ↗
testtest run

这个测试文件关心的是:用户选中的某个目录,到底能不能被当成插件。插件不是随便一个文件夹就算数,它必须有清单文件,比如 .codex-plugin/plugin.json。文件里先造了一个假的文件系统 SyntheticPluginFileSystem,它只会回答“这个目录存在吗”“清单文件能读吗”,其他操作一律说不支持。这样测试就能确认解析插件时真的走执行器提供的文件系统,而不是偷偷读本机硬盘。后面的几个异步测试分别检查:正常插件能被识别;普通目录不会被误认成插件;环境不存在时不能退回本机文件系统;首选清单坏了就直接报错,不能再去试备用清单;执行器里的路径必须是明确的绝对路径。整体上,这是给插件加载入口加护栏,避免安全边界和错误处理被破坏。

函数细节17
SyntheticPluginFileSystem::unsupported57–62 ↗
fn unsupported() -> FileSystemResult<T>

作用:这个小工具函数统一返回“这个文件系统操作不支持”。测试里的假文件系统只想模拟插件解析会用到的少数操作,其他操作如果被调用,就说明代码走偏了。

数据流:进去没有实际业务数据 → 它创建一个输入输出错误,错误类型是“不支持” → 出来是一个失败结果,告诉调用方这个操作不该在本测试里发生。

调用关系:它被假文件系统中大多数不用的接口方法借用,比如 canonicalizewrite_filecopy 等。这样这些方法不用各写一遍错误,也能清楚表达:插件解析流程不应该依赖这些能力。

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

SyntheticPluginFileSystem::canonicalize66–72 ↗
fn canonicalize(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, PathUri>

作用:这是假的“规范化路径”接口。规范化路径就是把路径整理成标准样子,但这个测试不希望插件解析用到它,所以这里直接报不支持。

数据流:进去是一个路径和可选的沙箱上下文 → 它没有真正处理路径,只把工作包装成异步结果 → 出来是 unsupported 产生的失败结果。

调用关系:它实现的是 ExecutorFileSystem 这个文件系统接口的一部分。若被插件解析流程意外调用,测试会因为“不支持”而暴露问题。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticPluginFileSystem::read_file74–91 ↗
fn read_file(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, Vec<u8>>

作用:这个函数模拟读取文件,主要用来假装读插件清单。它还会记录自己读过哪个路径,方便测试确认读取顺序和读取位置。

数据流:进去是一个文件路径 → 它先把路径转成绝对路径,记下一条 Read 调用记录;如果路径正好是测试预设的清单文件,就返回清单内容的字节;否则返回“找不到” → 出来要么是清单内容,要么是读取失败。

调用关系:在测试 plugin_root_resolution_uses_supplied_executor_file_system 里,resolve_plugin_root 解析插件时会通过这个假文件系统读取清单。测试最后会检查调用记录,确认它确实读的是执行器文件系统里的清单。

调用图:调用 1 个内部函数(to_abs_path);外部调用 4 个(pin, new, Read, clone)。

SyntheticPluginFileSystem::read_file_stream93–99 ↗
fn read_file_stream(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream>

作用:这是假的“流式读文件”接口。流式读就是一边读一边处理大文件,但插件解析不需要它,所以这里故意不支持。

数据流:进去是路径和可选沙箱上下文 → 它不读取任何内容,只返回一个异步失败 → 出来是“不支持”的错误。

调用关系:它只是为了完整实现 ExecutorFileSystem 接口。如果插件解析错误地改成用流式读取,这个测试会立刻失败。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticPluginFileSystem::write_file101–108 ↗
fn write_file(
        &'a self,
        _path: &'a PathUri,
        _contents: Vec<u8>,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, ()>

作用:这是假的写文件接口。插件解析只应该读信息,不应该写文件,所以这里直接拒绝。

数据流:进去是目标路径、要写入的字节内容和可选沙箱上下文 → 它忽略这些输入,不做磁盘修改 → 出来是“不支持”的错误。

调用关系:它作为假文件系统的一部分存在,目的是守住测试边界:解析插件时如果开始写东西,就是异常行为。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticPluginFileSystem::create_directory110–117 ↗
fn create_directory(
        &'a self,
        _path: &'a PathUri,
        _options: CreateDirectoryOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'

作用:这是假的创建目录接口。测试里的插件解析流程不应该新建目录,因此这个函数总是报不支持。

数据流:进去是目录路径、创建选项和可选沙箱上下文 → 它不创建任何目录 → 出来是“不支持”的错误。

调用关系:它补齐 ExecutorFileSystem 接口。只要解析流程意外尝试创建目录,测试就会因为这个函数的错误结果而发现问题。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticPluginFileSystem::get_metadata119–146 ↗
fn get_metadata(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, FileMetadata>

作用:这个函数模拟查询文件或目录的基本信息,比如“这是目录还是文件”。插件解析需要靠它判断插件根目录和清单文件是否存在。

数据流:进去是一个路径 → 它转成绝对路径,记下一条 Metadata 调用记录;如果路径是插件根目录,就返回“这是目录”;如果路径是清单文件,就返回“这是文件”;其他路径返回“找不到” → 出来是文件信息或错误。

调用关系:在 plugin_root_resolution_uses_supplied_executor_file_system 中,resolve_plugin_root 会先用它确认根目录,再确认清单文件。测试最后核对记录,确保检查顺序是根目录、清单文件、读取清单。

调用图:调用 1 个内部函数(to_abs_path);外部调用 4 个(pin, new, Metadata, clone)。

SyntheticPluginFileSystem::read_directory148–154 ↗
fn read_directory(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>>

作用:这是假的读取目录列表接口。插件解析不靠列目录来找东西,而是检查固定位置的清单,所以这里不支持。

数据流:进去是目录路径和可选沙箱上下文 → 它不列出任何目录内容 → 出来是“不支持”的错误。

调用关系:它用于保证插件解析不是靠扫描目录碰运气。如果未来代码改成读目录,这个测试会提醒维护者注意行为变化。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticPluginFileSystem::remove156–163 ↗
fn remove(
        &'a self,
        _path: &'a PathUri,
        _options: RemoveOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, ()>

作用:这是假的删除接口。解析插件绝不应该删除文件或目录,所以它总是拒绝。

数据流:进去是路径、删除选项和可选沙箱上下文 → 它不删除任何东西 → 出来是“不支持”的错误。

调用关系:它只是为了实现文件系统接口。若解析流程误触删除操作,测试会马上失败,避免危险行为被悄悄引入。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticPluginFileSystem::copy165–173 ↗
fn copy(
        &'a self,
        _source_path: &'a PathUri,
        _destination_path: &'a PathUri,
        _options: CopyOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> Ex

作用:这是假的复制接口。插件解析只需要确认和读取清单,不应该复制文件,因此这里报不支持。

数据流:进去是源路径、目标路径、复制选项和可选沙箱上下文 → 它不复制任何文件 → 出来是“不支持”的错误。

调用关系:它补全假文件系统接口,并作为一条警戒线:解析插件时如果需要复制文件,就不符合这些测试假设。

调用图:外部调用 2 个(pin, unsupported)。

write_manifest176–181 ↗
fn write_manifest(plugin_root: &Path, relative_path: &str, contents: &str)

作用:这个辅助函数在临时测试目录里写一个插件清单文件。测试用它快速搭出“有清单”“清单坏掉”“有备用清单”等场景。

数据流:进去是插件根目录、清单的相对路径和清单文本 → 它拼出完整路径,先创建父目录,再把文本写进文件 → 出来没有返回值,但磁盘上的临时目录多了一个清单文件。

调用关系:它被 unavailable_environment_does_not_fall_back_to_host_filesystemmalformed_preferred_manifest_does_not_fall_through_to_alternate 使用,用来准备真实临时文件,方便测试 Provider 面对本机文件时的行为。

调用图:被 2 处调用(malformed_preferred_manifest_does_not_fall_through_to_alternate, unavailable_environment_does_not_fall_back_to_host_filesystem);外部调用 3 个(join, create_dir_all, write)。

selected_root183–191 ↗
fn selected_root(id: &str, environment_id: &str, path: &Path) -> SelectedCapabilityRoot

作用:这个辅助函数生成一个“用户选中的能力根目录”。可以把它理解成一张地址卡:告诉系统这个目录叫什么、在哪个环境里、路径是什么。

数据流:进去是根目录 ID、环境 ID 和本机路径 → 它把路径转成字符串,包进 SelectedCapabilityRoot 结构里,并标明位置来自某个执行环境 → 出来是一份测试可直接传给解析器的选择记录。

调用关系:多个测试都用它来少写重复代码。它把测试数据整理成 resolveresolve_plugin_root 需要的格式,让每个测试能专心表达自己要验证的场景。

调用图:被 4 处调用(malformed_preferred_manifest_does_not_fall_through_to_alternate, plugin_root_resolution_uses_supplied_executor_file_system, standalone_capability_root_is_not_a_plugin, unavailable_environment_does_not_fall_back_to_host_filesystem);外部调用 1 个(to_string_lossy)。

plugin_root_resolution_uses_supplied_executor_file_system194–244 ↗
async fn plugin_root_resolution_uses_supplied_executor_file_system()

作用:这个测试确认:解析执行器里的插件时,系统真的使用传入的执行器文件系统,而不是偷偷去读本机文件系统。它还检查读取步骤是否符合预期。

数据流:开始时创建一个临时路径,但不在本机磁盘上创建插件目录 → 它准备好期望的清单解析结果,再构造只存在于假文件系统里的插件根目录和清单 → 调用 resolve_plugin_root 后,得到一个解析出的插件描述,并检查假文件系统记录到的调用顺序。

调用关系:它直接测试底层的 resolve_plugin_root。过程中用 selected_root 做输入,用 parse_plugin_manifest 生成期望结果,最后验证 SyntheticPluginFileSystem::get_metadataSyntheticPluginFileSystem::read_file 被按顺序使用。

调用图:调用 3 个内部函数(parse_plugin_manifest, selected_root, from_absolute_path_checked);外部调用 6 个(new, new, assert!, assert_eq!, resolve_plugin_root, tempdir)。

standalone_capability_root_is_not_a_plugin247–263 ↗
async fn standalone_capability_root_is_not_a_plugin()

作用:这个测试确认:一个普通目录即使被用户选中,也不会自动被当成插件。只有带正确插件清单的位置才会被识别。

数据流:开始时创建一个真实临时目录,但不写插件清单 → 它用默认测试环境创建插件提供器,再把这个目录作为选中的根传进去 → 出来是 None,意思是“这里没有插件”。

调用关系:它测试 ExecutorPluginProvider::resolve 的正常否定路径。selected_root 负责构造输入,EnvironmentManager::default_for_tests 提供本地测试环境,最后断言结果不是插件。

调用图:调用 3 个内部函数(new, selected_root, default_for_tests);外部调用 4 个(new, assert_eq!, create_dir_all, tempdir)。

unavailable_environment_does_not_fall_back_to_host_filesystem266–282 ↗
async fn unavailable_environment_does_not_fall_back_to_host_filesystem()

作用:这个测试确认:如果用户选的环境不存在,系统必须报错,不能退而求其次去读本机硬盘上的同一路径。这个行为对隔离和安全很重要。

数据流:开始时在本机临时目录里真的写了一个插件清单 → 但它创建的环境管理器没有任何可用环境 → 调用解析时使用一个不存在的环境 ID → 出来是明确错误,说明这个环境不可用,而不是解析成本机插件。

调用关系:它用 write_manifest 布置一个容易误读成功的陷阱,再用 ExecutorPluginProvider::resolve 验证 Provider 不会掉进这个陷阱。selected_root 提供带缺失环境 ID 的输入。

调用图:调用 4 个内部函数(new, selected_root, write_manifest, without_environments);外部调用 3 个(new, assert_eq!, tempdir)。

malformed_preferred_manifest_does_not_fall_through_to_alternate285–320 ↗
async fn malformed_preferred_manifest_does_not_fall_through_to_alternate()

作用:这个测试确认:首选位置的插件清单如果存在但格式坏了,系统要直接报错,不能悄悄改去读备用位置的清单。这样能避免隐藏真正的问题。

数据流:开始时在 .codex-plugin/plugin.json 写入坏 JSON,又在 .claude-plugin/plugin.json 写入正常清单 → 调用 Provider 解析插件 → 出来是首选清单的解析错误,并且错误里带着首选清单路径和根目录 ID。

调用关系:它用 write_manifest 同时准备坏的首选清单和好的备用清单,然后让 ExecutorPluginProvider::resolve 做判断。测试通过匹配 ExecutorPluginProviderError::ParseManifest 来确认错误没有被备用清单掩盖。

调用图:调用 5 个内部函数(new, selected_root, write_manifest, default_for_tests, from_absolute_path_checked);外部调用 4 个(new, assert_eq!, panic!, tempdir)。

executor_root_must_be_an_explicit_absolute_path323–342 ↗
async fn executor_root_must_be_an_explicit_absolute_path()

作用:这个测试确认:执行器环境里的插件路径必须是明确的绝对路径,不能写成 ~/plugins/demo 这种依赖个人主目录展开的形式。这样可以避免不同机器或环境解释路径不一致。

数据流:进去的测试数据是一个路径字符串 ~/plugins/demo,看起来像家目录相对路径,但不是绝对路径 → 它把这份选择记录交给 Provider 解析 → 出来是错误信息,明确说执行器路径必须是绝对路径。

调用关系:它直接构造 SelectedCapabilityRoot,不用 selected_root,因为要保留这个特殊的非法路径字符串。随后调用 ExecutorPluginProvider::resolve,检查路径校验这道门是否挡住了错误输入。

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

core-plugins/src/store_tests.rs源码 ↗
testtest run

这个文件不提供正式功能,而是用临时文件夹假装成用户的插件目录,造出一个个小插件,再检查 PluginStore 的行为是否符合预期。测试会写入插件说明文件 plugin.json、技能文件 SKILL.md 和配置文件 .mcp.json,然后让安装逻辑去复制、命名、选版本。它重点盯三类问题:第一,路径要安全,不能接受相对路径或带 ../ 的名字;第二,安装位置要稳定,比如缓存目录按“市场名/插件名/版本”组织;第三,版本选择要合理,本地版 local 优先,没有 local 时选最新版本,并且安装新版本时会清掉可能继续抢占生效位置的旧版本。简单说,它是在确认插件仓库像一个有规矩的货架:货物标签、摆放位置、过期清理都不能乱。

函数细节20
write_plugin_with_version6–25 ↗
fn write_plugin_with_version(
    root: &Path,
    dir_name: &str,
    manifest_name: &str,
    manifest_version: Option<&str>,
)

作用:这是测试用的小工具,用来在临时目录里造一个假的插件。它可以顺便给插件说明文件写入指定版本,方便后面的测试模拟不同版本的插件。

数据流:进去的是根目录、插件文件夹名、plugin.json 里的插件名,以及可选版本号;它创建插件目录、.codex-plugin 目录和 skills 目录,写入 plugin.json、SKILL.md、.mcp.json;出来没有返回值,但磁盘上多了一个可被安装逻辑识别的假插件。

调用关系:它是很多安装和版本测试的准备步骤。write_plugin 会把简单场景转交给它;需要指定版本或空版本的测试,比如 install_uses_manifest_version_when_present、install_rejects_blank_manifest_version、install_with_new_version_keeps_existing_plugin_root_and_prunes_old_versions,会直接调用它。

调用图:被 4 处调用(install_rejects_blank_manifest_version, install_uses_manifest_version_when_present, install_with_new_version_keeps_existing_plugin_root_and_prunes_old_versions, write_plugin);外部调用 4 个(join, format!, create_dir_all, write)。

write_plugin27–34 ↗
fn write_plugin(root: &Path, dir_name: &str, manifest_name: &str)

作用:这是更简单的造插件工具,用来创建没有显式版本号的假插件。大多数测试只关心名字和目录,所以用它就够了。

数据流:进去的是根目录、目录名和插件名;它把版本号留空,然后调用 write_plugin_with_version;结果是在临时目录里生成一个默认会被当成 local 版本的测试插件。

调用关系:它处在测试准备流程的最前面,给安装、路径、安全检查等测试提供样本插件。它自己不写细节,而是把真正的建目录和写文件工作交给 write_plugin_with_version。

调用图:调用 1 个内部函数(write_plugin_with_version);被 9 处调用(active_plugin_version_compares_semver_versions_semantically, active_plugin_version_prefers_default_local_version_when_multiple_versions_exist, active_plugin_version_reads_version_directory_name, active_plugin_version_returns_latest_version_when_default_is_missing, install_copies_plugin_into_default_marketplace, install_rejects_manifest_names_that_do_not_match_marketplace_plugin_name, install_rejects_manifest_names_with_path_separators, install_uses_manifest_name_for_destination_and_key, install_with_version_uses_requested_cache_version)。

try_new_rejects_relative_codex_home37–46 ↗
fn try_new_rejects_relative_codex_home()

作用:这个测试确认 PluginStore 不接受相对路径作为 Codex 主目录。这样可以避免程序把插件缓存建到不确定的位置。

数据流:进去的是一个写死的相对路径 relative;它调用 PluginStore::try_new,期待得到错误;最后把错误信息标准化后比对,确认错误明确指出路径不是绝对路径。

调用关系:这是初始化阶段的安全测试。它直接测试 try_new 的失败分支,不依赖假插件,也不进入安装流程。

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

install_copies_plugin_into_default_marketplace49–72 ↗
fn install_copies_plugin_into_default_marketplace()

作用:这个测试确认安装一个普通本地插件时,文件会被复制到默认规则下的缓存目录。它保证安装后 plugin.json 和技能文件真的存在。

数据流:先创建临时目录和假插件,再构造插件编号 PluginId;调用 PluginStore::new 后执行 install;最后检查返回的插件编号、版本 local、安装路径,以及目标目录里的关键文件。

调用关系:它覆盖最基础的安装成功流程。准备工作由 write_plugin 完成,真正安装交给 PluginStore 的 install,测试用断言确认结果对象和磁盘文件都正确。

调用图:调用 4 个内部函数(new, write_plugin, new, try_from);外部调用 3 个(assert!, assert_eq!, tempdir)。

install_uses_manifest_name_for_destination_and_key75–98 ↗
fn install_uses_manifest_name_for_destination_and_key()

作用:这个测试确认插件最终存放位置用的是 plugin.json 里的名字,而不是源文件夹名字。这样即使下载目录叫 source-dir,真正登记的插件名也不会乱。

数据流:它创建一个目录名为 source-dir、清单名为 manifest-name 的插件;安装时传入对应的 PluginId;安装完成后检查返回路径是否落在 plugins/cache/market/manifest-name/local。

调用关系:它测试 install 读取插件清单后决定目标位置的行为。write_plugin 负责制造“目录名和清单名不同”的场景,install 负责解析并放置插件。

调用图:调用 4 个内部函数(new, write_plugin, new, try_from);外部调用 2 个(assert_eq!, tempdir)。

plugin_root_derives_path_from_key_and_version101–110 ↗
fn plugin_root_derives_path_from_key_and_version()

作用:这个测试确认 PluginStore 能根据插件编号和版本号算出插件安装根目录。也就是验证“这件货该放到货架哪一格”的路径规则。

数据流:进去的是临时 Codex 主目录、插件名 sample、市场名 debug、版本 local;它调用 plugin_root 计算路径;出来的结果要等于 plugins/cache/debug/sample/local。

调用关系:它不安装文件,只检查路径计算。这个路径规则会被安装、查找已安装插件等流程依赖。

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

plugin_data_root_derives_path_from_key113–122 ↗
fn plugin_data_root_derives_path_from_key()

作用:这个测试确认插件自己的数据目录按插件编号生成,而不是和安装缓存目录混在一起。这样插件代码和插件运行数据可以分开放。

数据流:进去的是临时主目录和 PluginId;它调用 plugin_data_root;结果应该是 plugins/data/sample-debug 这样的目录路径。

调用关系:它测试数据目录的命名规则。这个规则独立于安装版本,因为数据通常跟插件身份有关,而不是跟某个缓存版本强绑定。

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

install_with_version_uses_requested_cache_version125–152 ↗
fn install_with_version_uses_requested_cache_version()

作用:这个测试确认当调用方明确指定缓存版本时,安装目录会使用这个版本号。它用于模拟从市场下载到某个固定版本或哈希的插件。

数据流:它先造一个没有版本号的插件,再指定 plugin_version 为一串字符;调用 install_with_version;最后确认结果里的版本和目标目录都使用这串指定版本,并且 plugin.json 被复制过去。

调用关系:它测试 install_with_version 这条显式版本安装路线。write_plugin 只负责造源插件,版本选择不看清单,而是由调用 install_with_version 时传入的参数决定。

调用图:调用 4 个内部函数(new, write_plugin, new, try_from);外部调用 4 个(assert!, assert_eq!, format!, tempdir)。

install_uses_manifest_version_when_present155–184 ↗
fn install_uses_manifest_version_when_present()

作用:这个测试确认 plugin.json 里写了版本号时,普通 install 会采用这个版本号,而不是默认 local。这样插件包自己声明的版本能被尊重。

数据流:它创建一个清单版本为 1.2.3-beta+7 的假插件;调用 install;结果应该显示插件版本是这个清单版本,安装路径也以这个版本结尾,并且文件存在。

调用关系:它覆盖 install 的版本读取逻辑。准备阶段由 write_plugin_with_version 写入版本,安装阶段读取 plugin.json 并决定缓存目录名。

调用图:调用 4 个内部函数(new, write_plugin_with_version, new, try_from);外部调用 3 个(assert!, assert_eq!, tempdir)。

install_rejects_blank_manifest_version187–204 ↗
fn install_rejects_blank_manifest_version()

作用:这个测试确认 plugin.json 里的版本号不能只是空白字符。这样可以防止出现看起来有版本、实际无法管理的插件目录。

数据流:它创建一个版本字段为几个空格的插件;调用 install;期待失败,并检查错误信息是“插件版本不能为空”。

调用关系:它测试安装前的输入校验。write_plugin_with_version 制造坏清单,install 读取后必须拒绝,而不是继续复制文件。

调用图:调用 4 个内部函数(new, write_plugin_with_version, new, try_from);外部调用 2 个(assert_eq!, tempdir)。

active_plugin_version_reads_version_directory_name207–225 ↗
fn active_plugin_version_reads_version_directory_name()

作用:这个测试确认 PluginStore 能从缓存目录里的版本文件夹名判断当前可用版本。也就是说,不需要额外数据库,也能知道装了哪个版本。

数据流:它直接在 plugins/cache/debug/sample-plugin/local 下写入一个假插件;然后调用 active_plugin_version 和 active_plugin_root;结果应分别返回 local 和对应目录。

调用关系:它测试已安装插件的发现逻辑。write_plugin 直接把目录摆成缓存结构,PluginStore 只负责扫描和推断当前生效位置。

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

active_plugin_version_prefers_default_local_version_when_multiple_versions_exist228–247 ↗
fn active_plugin_version_prefers_default_local_version_when_multiple_versions_exist()

作用:这个测试确认如果 local 版本和其他版本同时存在,local 会优先生效。local 可以理解成本地开发版,系统优先让它覆盖市场缓存版本。

数据流:它在同一个插件下面创建一个哈希版本目录和一个 local 目录;调用 active_plugin_version;结果必须是 local。

调用关系:它测试版本选择的优先级。write_plugin 负责造出多个版本目录,active_plugin_version 负责按规则选出当前版本。

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

active_plugin_version_returns_latest_version_when_default_is_missing250–269 ↗
fn active_plugin_version_returns_latest_version_when_default_is_missing()

作用:这个测试确认没有 local 版本时,PluginStore 会在已有版本里选最新的那个。这样用户不会意外继续使用较旧缓存。

数据流:它创建两个非 local 的版本目录:0123456789abcdef 和 fedcba9876543210;调用 active_plugin_version;结果应该选择排序更靠后的 fedcba9876543210。

调用关系:它测试没有默认 local 时的后备选择规则。目录由 write_plugin 伪造,选择动作由 active_plugin_version 完成。

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

active_plugin_version_compares_semver_versions_semantically272–291 ↗
fn active_plugin_version_compares_semver_versions_semantically()

作用:这个测试确认语义化版本号会按版本含义比较,而不是按字符串字母顺序比较。语义化版本就是 10.0.0 这种“主版本.次版本.补丁版本”的编号规则。

数据流:它创建 9.0.0 和 10.0.0 两个版本目录;调用 active_plugin_version;结果必须是 10.0.0,避免把字符串开头较大的 9 错当成更新。

调用关系:它测试版本选择里的比较规则。write_plugin 提供两个版本目录,active_plugin_version 需要用更聪明的版本比较选出真正较新的版本。

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

install_with_new_version_keeps_existing_plugin_root_and_prunes_old_versions294–329 ↗
fn install_with_new_version_keeps_existing_plugin_root_and_prunes_old_versions()

作用:这个测试确认安装新版本后,新版本会生效,并且可能继续抢占生效位置的旧版本会被删掉。这样缓存目录不会留下会干扰选择的旧货。

数据流:它先安装 1.0.0,再安装 2.0.0;之后检查当前版本是 2.0.0,2.0.0 目录存在,1.0.0 目录已经不存在。

调用关系:它覆盖一次插件升级的完整故事。write_plugin_with_version 分别制造两个版本,install 连续执行两次,active_plugin_version 和文件存在性检查共同确认清理逻辑生效。

调用图:调用 4 个内部函数(new, write_plugin_with_version, new, try_from);外部调用 3 个(assert!, assert_eq!, tempdir)。

old_plugin_version_would_stay_active_for_local_or_later_versions332–339 ↗
fn old_plugin_version_would_stay_active_for_local_or_later_versions()

作用:这个测试检查一个辅助判断:旧版本在某些情况下是否会继续被选为当前版本。它用来决定安装新版本时是否需要清理旧目录。

数据流:它给 old_plugin_version_would_stay_active 传入几组旧版本和新版本:local 对 1.0.0、10.0.0 对 9.0.0、1.0.0 对 2.0.0;结果分别应为真、真、假。

调用关系:它直接测试版本清理策略里的小判断函数。安装升级测试会体现这个策略的实际效果,而这里把判断规则单独拎出来确认。

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

plugin_root_rejects_path_separators_in_key_segments342–354 ↗
fn plugin_root_rejects_path_separators_in_key_segments()

作用:这个测试确认插件名和市场名里不能包含路径分隔或 ../ 这类危险内容。这样可以防止插件名把安装路径“拐”到系统其他目录。

数据流:它尝试解析 ../../etc@debug 和 sample@../../etc;两次都应该失败;最后比对错误信息,确认分别指出插件名或市场名不合法。

调用关系:它测试 PluginId::parse 的安全校验。这个校验发生在生成插件路径之前,是防止路径穿越的第一道门。

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

install_rejects_manifest_names_with_path_separators357–372 ↗
fn install_rejects_manifest_names_with_path_separators()

作用:这个测试确认即使插件清单 plugin.json 里写了危险名字,安装时也会拒绝。不能只相信市场传来的 PluginId,也要检查插件包自己的名字。

数据流:它创建一个清单名为 ../../etc 的假插件;调用 install;期待失败,并确认错误说插件名只能包含安全字符。

调用关系:它测试 install 读取清单后的安全检查。write_plugin 制造坏清单,install 必须在决定目标路径前拦住它。

调用图:调用 4 个内部函数(new, write_plugin, new, try_from);外部调用 2 个(assert_eq!, tempdir)。

install_rejects_marketplace_names_with_path_separators375–382 ↗
fn install_rejects_marketplace_names_with_path_separators()

作用:这个测试确认市场名也不能包含 ../ 这类路径内容。因为市场名会成为缓存路径的一部分,放开它会带来路径逃逸风险。

数据流:它直接用 sample-plugin 和 ../../etc 创建 PluginId;创建过程应失败;最后确认错误信息说明市场名不合法。

调用关系:它测试 PluginId::new 的输入校验。这个检查比安装更早,确保危险的插件身份根本不能被构造出来。

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

install_rejects_manifest_names_that_do_not_match_marketplace_plugin_name385–400 ↗
fn install_rejects_manifest_names_that_do_not_match_marketplace_plugin_name()

作用:这个测试确认 plugin.json 里的插件名必须和市场请求的插件名一致。这样可以防止下载到的包“挂羊头卖狗肉”。

数据流:它创建一个清单名为 manifest-name 的插件,但安装时传入的 PluginId 名字是 different-name;调用 install 后应失败,并返回说明两个名字不匹配的错误。

调用关系:它测试安装时的身份核对。write_plugin 准备名字不一致的插件包,install 读取清单后与 PluginId 对照,发现不一致就停止。

调用图:调用 4 个内部函数(new, write_plugin, new, try_from);外部调用 2 个(assert_eq!, tempdir)。

core-plugins/src/loader_tests.rs源码 ↗
testtest run

插件系统要从用户配置、磁盘目录、插件清单、钩子文件、Git 仓库等很多地方拼出最终结果,任何一步出错都会让插件该启用的不启用,或把不该加载的能力也加载进来。这个测试文件用临时目录假装真实用户环境,写入假的配置文件、插件清单和钩子文件,然后调用真正的加载函数,看结果是不是符合预期。它重点检查几类事:多个用户配置层能不能合并;只加载 hooks(钩子,也就是某些事件发生时自动执行的小动作)时,是否只解析钩子而不加载技能、MCP 服务和应用;Git 版本号是否正确缩短;插件清单里不同写法的 hooks 是否都能被发现;坏 JSON 是否只给警告而不直接崩掉;从 Git 仓库只取某个子目录时,是否真的没有把整个仓库都拉下来。

函数细节19
user_config_path12–15 ↗
fn user_config_path(temp_dir: &TempDir, file_name: &str) -> AbsolutePathBuf

作用:把临时测试目录里的文件名变成一个“绝对路径”。测试里需要模拟真实用户配置文件的位置,所以先把路径整理成加载器能接受的格式。

数据流:输入是一个临时目录和文件名;它读取临时目录的实际磁盘路径,把文件名拼上去,再转换成 AbsolutePathBuf(表示确定无误的绝对路径);输出就是这个配置文件的绝对路径,如果路径不是绝对路径会让测试直接失败。

调用关系:它是测试用的小工具,主要配合 user_layer 使用。测试在创建配置层之前,先用它生成像 config.toml、work.config.toml 这样的用户配置文件路径。

调用图:调用 1 个内部函数(from_absolute_path);外部调用 1 个(path)。

user_layer17–25 ↗
fn user_layer(path: AbsolutePathBuf, config: &str) -> ConfigLayerEntry

作用:把一段 TOML 配置文本包装成一个“用户配置层”。TOML 是一种常见配置文件格式,长得像 ini 文件,用来写键值和表格。

数据流:输入是配置文件路径和一段 TOML 字符串;它先把字符串解析成配置数据,再标记这份数据来自用户文件;输出是 ConfigLayerEntry,也就是配置合并栈里的一层。

调用关系:它被需要构造配置栈的测试使用,尤其是配置合并和插件启用状态相关的测试。它把纯文本配置变成 ConfigLayerStack::new 能吃的材料。

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

configured_plugins_from_stack_merges_user_layers28–67 ↗
fn configured_plugins_from_stack_merges_user_layers()

作用:确认多个用户配置层里的插件设置会合并在一起,而不是后一层把前一层全部盖掉。这样用户的基础配置和工作区配置才能同时生效。

数据流:测试先创建临时目录,再造出两层用户配置:一层启用 base 插件,另一层禁用 profile 插件;然后把它们放进配置栈,调用 configured_plugins_from_stack;最后检查输出的插件表同时包含 base 和 profile,并且启用状态正确。

调用关系:这是配置合并规则的守门测试。它用 user_config_path 和 user_layer 准备输入,再把结果交给断言比较,确保配置加载器的合并行为符合预期。

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

hooks_only_scope_shares_plugin_resolution_without_loading_other_capabilities70–210 ↗
async fn hooks_only_scope_shares_plugin_resolution_without_loading_other_capabilities()

作用:确认“只加载钩子”的模式仍然会用同一套插件查找和校验规则,但不会顺手加载技能、MCP 服务或应用。这样启动时只需要钩子的场景可以更轻、更安全。

数据流:测试在临时目录里造出多个插件:有效插件、被禁用插件、清单坏掉的插件、缺失插件、钩子文件坏掉的插件;再写入用户配置说明哪些插件启用;随后分别执行完整加载和 HooksOnly 加载;最后比较两种加载在启用状态、根目录、错误和钩子警告上的结果一致,同时确认 HooksOnly 对有效插件没有加载 manifest 名称、技能目录、MCP 服务和应用。

调用关系:这是插件加载流程里一个重要分支的测试。它直接驱动 load_plugins_from_layer_stack 和 load_plugins_from_layer_stack_with_scope,验证二者共享插件解析结果,但 HooksOnly 会跳过其他能力的读取。

调用图:调用 2 个内部函数(new, new);外部调用 8 个(new, new, default, assert!, assert_eq!, default, write_file, vec!)。

curated_plugin_cache_version_shortens_full_git_sha213–218 ↗
fn curated_plugin_cache_version_shortens_full_git_sha()

作用:确认完整的 Git 提交哈希会被缩短成前 8 位。Git 提交哈希就是代码版本的身份证,完整值很长,缓存目录用短一点更好读。

数据流:输入是一串 40 位的 Git 哈希;测试调用 curated_plugin_cache_version;输出应是前 8 个字符,测试用断言确认它等于 01234567。

调用关系:它单独检查缓存版本命名的小规则。这个规则影响精选插件下载后放在哪个缓存目录里。

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

curated_plugin_cache_version_preserves_non_git_sha_versions221–227 ↗
fn curated_plugin_cache_version_preserves_non_git_sha_versions()

作用:确认不是完整 Git 哈希的版本名不会被乱改。比如 export-backup 这种人工版本名,应该原样保留。

数据流:输入是普通字符串版本名和一个长度不够的短数字串;测试分别调用 curated_plugin_cache_version;输出应与输入完全相同。

调用关系:它和 curated_plugin_cache_version_shortens_full_git_sha 配成一组,说明只有像完整 Git 哈希的字符串才会被缩短,其他版本名不会被误处理。

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

plugin_id229–231 ↗
fn plugin_id() -> PluginId

作用:生成测试里统一使用的插件 ID。插件 ID 可以理解成插件的完整名字,里面包含插件名和来源市场。

数据流:它没有外部输入;内部把 demo-plugin@test-marketplace 这段文字解析成 PluginId;输出是后续加载钩子时使用的插件身份。

调用关系:它被 load_sources 调用。这样所有钩子来源都能带上同一个插件身份,assert_sources 也能用它检查结果是否一致。

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

plugin_root233–240 ↗
fn plugin_root() -> (tempfile::TempDir, AbsolutePathBuf)

作用:创建一个临时插件目录,并准备好插件通常需要的子目录。这样每个测试都有干净、独立的假插件,不会互相污染。

数据流:它没有业务输入;内部创建临时目录,在里面建立 demo-plugin、.codex-plugin 和 hooks 文件夹;输出是临时目录句柄和插件根目录的绝对路径。临时目录句柄活着时,测试文件才不会被提前删除。

调用关系:多个 hooks 测试都会先调用它准备现场,然后再写清单和钩子文件。它是这些测试的共同地基。

调用图:调用 1 个内部函数(try_from);被 6 处调用(load_plugin_hooks_discovers_default_hooks_file, load_plugin_hooks_manifest_paths_replace_default_hooks_file, load_plugin_hooks_reports_invalid_hook_file, load_plugin_hooks_supports_inline_manifest_hook_list, load_plugin_hooks_supports_inline_manifest_hooks, load_plugin_hooks_supports_manifest_hook_path);外部调用 2 个(create_dir_all, tempdir)。

write_manifest242–244 ↗
fn write_manifest(plugin_root: &AbsolutePathBuf, manifest: &str)

作用:把一段插件清单 JSON 写到标准位置。插件清单像插件的说明书,告诉加载器插件叫什么、钩子在哪里等。

数据流:输入是插件根目录和清单文本;它把文本写进 .codex-plugin/plugin.json;结果是在临时插件目录里出现一份清单文件。

调用关系:它被多个 hooks 测试调用,用来快速切换不同清单写法。写好后,load_sources 会读取这份清单并继续加载钩子。

调用图:调用 1 个内部函数(join);被 6 处调用(load_plugin_hooks_discovers_default_hooks_file, load_plugin_hooks_manifest_paths_replace_default_hooks_file, load_plugin_hooks_reports_invalid_hook_file, load_plugin_hooks_supports_inline_manifest_hook_list, load_plugin_hooks_supports_inline_manifest_hooks, load_plugin_hooks_supports_manifest_hook_path);外部调用 1 个(write)。

write_hook_file246–262 ↗
fn write_hook_file(plugin_root: &AbsolutePathBuf, relative_path: &str, event: &str, command: &str)

作用:按指定事件和命令生成一个钩子 JSON 文件。钩子就是在某个时机触发的小动作,比如运行一条命令。

数据流:输入是插件根目录、相对路径、事件名和命令;它拼出一段符合钩子格式的 JSON,然后写到指定文件;结果是磁盘上多了一个可被加载器读取的钩子文件。

调用关系:它被测试“清单指定 hooks 路径”和“清单路径替换默认 hooks 文件”时使用,帮助快速生成 one.json、two.json 这类测试文件。

调用图:调用 1 个内部函数(join);被 2 处调用(load_plugin_hooks_manifest_paths_replace_default_hooks_file, load_plugin_hooks_supports_manifest_hook_path);外部调用 2 个(format!, write)。

load_sources264–280 ↗
fn load_sources(plugin_root: &AbsolutePathBuf) -> (Vec<PluginHookSource>, Vec<String>)

作用:用真实加载逻辑读取一个测试插件的钩子来源。它把前面写好的清单和钩子文件送进被测函数,拿回加载结果和警告。

数据流:输入是插件根目录;它先读取 plugin.json 清单,再构造插件数据目录路径,并生成测试插件 ID;然后调用 load_plugin_hooks;输出是一组 PluginHookSource(钩子来源)和一组警告文本。

调用关系:它是多个 hooks 测试的中间桥梁:测试先用 plugin_root、write_manifest、write_hook_file 搭好文件,再通过它调用真正的加载函数,最后用 assert_sources 或直接断言检查结果。

调用图:调用 4 个内部函数(plugin_id, load_plugin_manifest, as_path, try_from);被 6 处调用(load_plugin_hooks_discovers_default_hooks_file, load_plugin_hooks_manifest_paths_replace_default_hooks_file, load_plugin_hooks_reports_invalid_hook_file, load_plugin_hooks_supports_inline_manifest_hook_list, load_plugin_hooks_supports_inline_manifest_hooks, load_plugin_hooks_supports_manifest_hook_path)。

assert_sources282–304 ↗
fn assert_sources(sources: &[PluginHookSource], expected_relative_paths: &[&str])

作用:统一检查加载出来的钩子来源是否符合预期。它避免每个测试都手写一大串重复断言。

数据流:输入是实际加载出的钩子来源列表和预期的相对路径列表;它检查每个来源的插件 ID 是否都是测试插件、来源路径是否按预期排列、每个来源是否正好有一个处理器;没有返回值,检查失败会让测试失败。

调用关系:它被大多数成功加载 hooks 的测试调用。load_sources 负责拿结果,它负责判断这些结果是不是对的。

调用图:被 5 处调用(load_plugin_hooks_discovers_default_hooks_file, load_plugin_hooks_manifest_paths_replace_default_hooks_file, load_plugin_hooks_supports_inline_manifest_hook_list, load_plugin_hooks_supports_inline_manifest_hooks, load_plugin_hooks_supports_manifest_hook_path);外部调用 1 个(assert_eq!)。

load_plugin_hooks_discovers_default_hooks_file307–329 ↗
fn load_plugin_hooks_discovers_default_hooks_file()

作用:确认如果清单没有特别指定 hooks,加载器会自动寻找默认的 hooks/hooks.json 文件。这是最常见、最省配置的插件写法。

数据流:测试先创建插件根目录,写一个只包含名字的清单,再写默认 hooks/hooks.json;然后调用 load_sources;输出应没有警告,并且只发现 hooks/hooks.json 这一份钩子来源。

调用关系:它使用 plugin_root 和 write_manifest 搭建默认结构,用 load_sources 调用真实加载逻辑,再用 assert_sources 检查默认发现规则。

调用图:调用 4 个内部函数(assert_sources, load_sources, plugin_root, write_manifest);外部调用 2 个(assert_eq!, write)。

load_plugin_hooks_supports_manifest_hook_path332–347 ↗
fn load_plugin_hooks_supports_manifest_hook_path()

作用:确认插件清单可以指定一个自定义 hooks 文件路径。这样插件作者不一定非要把钩子放在默认文件里。

数据流:测试写入一个带 hooks: ./hooks/one.json 的清单,再写对应的 one.json;调用 load_sources 后,结果应没有警告,并且来源路径是 hooks/one.json。

调用关系:它用 write_manifest 表达清单里的自定义路径,用 write_hook_file 创建文件,最后通过 load_sources 和 assert_sources 验证加载器听从清单设置。

调用图:调用 5 个内部函数(assert_sources, load_sources, plugin_root, write_hook_file, write_manifest);外部调用 1 个(assert_eq!)。

load_plugin_hooks_manifest_paths_replace_default_hooks_file350–372 ↗
fn load_plugin_hooks_manifest_paths_replace_default_hooks_file()

作用:确认只要清单明确列出 hooks 路径,默认的 hooks/hooks.json 就不会再被顺便加载。这样插件作者可以明确控制哪些钩子生效。

数据流:测试写入一个清单,指定 one.json 和 two.json;同时故意写一个默认 hooks/hooks.json;调用 load_sources 后,输出应只包含 one.json 和 two.json,没有默认文件,也没有警告。

调用关系:它把默认文件和清单指定文件同时放进插件目录,专门验证优先级规则:清单显式路径会替代默认发现。

调用图:调用 5 个内部函数(assert_sources, load_sources, plugin_root, write_hook_file, write_manifest);外部调用 1 个(assert_eq!)。

load_plugin_hooks_supports_inline_manifest_hooks375–398 ↗
fn load_plugin_hooks_supports_inline_manifest_hooks()

作用:确认 hooks 可以直接写在插件清单里,而不是单独放文件。这样小插件可以少建一个文件。

数据流:测试写入一个清单,里面直接包含 SessionStart 事件的钩子配置;调用 load_sources 后,应没有警告,并生成一个来源名为 plugin.json#hooks[0] 的钩子来源,表示它来自清单内部。

调用关系:它不写单独 hooks 文件,只靠 write_manifest 准备输入。load_sources 读取清单后,验证加载器能识别内嵌 hooks。

调用图:调用 4 个内部函数(assert_sources, load_sources, plugin_root, write_manifest);外部调用 1 个(assert_eq!)。

load_plugin_hooks_reports_invalid_hook_file401–416 ↗
fn load_plugin_hooks_reports_invalid_hook_file()

作用:确认钩子文件格式坏了时,加载器会给出清楚警告,而不是崩溃或假装成功。坏 JSON 指的是文件内容不符合 JSON 语法。

数据流:测试创建默认清单,再写一个内容为 { not-json 的坏 hooks 文件;调用 load_sources 后,输出的钩子来源应为空,警告列表里应包含具体解析错误和文件路径。

调用关系:它测试错误处理路径。和成功加载测试不同,它不调用 assert_sources,而是直接检查 sources 为空、warnings 文本准确。

调用图:调用 3 个内部函数(load_sources, plugin_root, write_manifest);外部调用 2 个(assert_eq!, write)。

load_plugin_hooks_supports_inline_manifest_hook_list419–452 ↗
fn load_plugin_hooks_supports_inline_manifest_hook_list()

作用:确认清单里可以一次内嵌多组 hooks。这样一个插件能把不同事件的钩子分成多个块来写。

数据流:测试写入一个清单,其中 hooks 是一个列表,第一项包含 SessionStart,第二项包含 Stop;调用 load_sources 后,应没有警告,并得到 plugin.json#hooks[0] 和 plugin.json#hooks[1] 两个来源。

调用关系:它验证内嵌 hooks 的列表写法。流程仍然是 plugin_root 建目录、write_manifest 写清单、load_sources 加载、assert_sources 检查。

调用图:调用 4 个内部函数(assert_sources, load_sources, plugin_root, write_manifest);外部调用 1 个(assert_eq!)。

materialize_git_subdir_uses_sparse_checkout455–499 ↗
fn materialize_git_subdir_uses_sparse_checkout()

作用:确认从 Git 仓库安装插件时,如果只指定仓库里的某个子目录,加载器会只取那个子目录。稀疏检出可以理解成“只拿货架上的一个盒子,不把整个仓库搬回家”。

数据流:测试先创建一个临时 Git 仓库,里面有目标插件目录、另一个插件目录和根目录文件;提交后调用 materialize_marketplace_plugin_source,要求只取 plugins/toolkit;输出的物化目录应包含目标 marker.txt,但检出根目录里不应有 root.txt,也不应有 plugins/other/marker.txt。

调用关系:它搭建真实 Git 仓库并调用插件来源物化逻辑,专门验证 Git 子目录下载路径。断言部分检查结果目录,确保实现没有偷偷完整检出整个仓库。

调用图:外部调用 5 个(assert!, assert_eq!, create_dir_all, write, tempdir)。

core-plugins/src/marketplace_tests.rs源码 ↗
testtest

这个文件不负责正式运行插件市场,而是像一套验收清单。它会临时造出一些假的仓库、假的 marketplace.json 和 plugin.json,然后调用真正的插件市场读取函数,看结果是不是符合预期。这里重点检查几类容易出错的事:市场文件可能放在旧位置或新位置;插件来源可能是本地目录、Git 仓库、Git 仓库里的子目录;相对路径不能偷偷跑到市场目录外面;坏插件不能拖垮整个市场列表;同名市场和同名插件要按约定处理;插件界面里的图片路径要变成安全的绝对路径。可以把它理解成给插件市场入口装了一排安检门:正常情况要放行,危险路径、无效名字、不可安装产品要拦住。

函数细节30
write_alternate_marketplace10–15 ↗
fn write_alternate_marketplace(repo_root: &Path, contents: &str) -> AbsolutePathBuf

作用:这个小工具函数用来在测试仓库里写一个备用布局的插件市场文件。测试里反复需要造这种文件,所以把重复的建目录、写文件动作收在这里。

数据流:输入一个仓库根目录和一段 JSON 字符串 → 它把路径拼成 .claude-plugin/marketplace.json,先创建父目录,再把内容写进去 → 返回这个市场文件的绝对路径,方便后面的测试拿去读取。

调用关系:它是测试辅助工具,不是正式功能。多个测试先用它搭好备用格式的市场文件,再去验证列市场或找插件的代码是否支持这种布局。

调用图:调用 1 个内部函数(try_from);被 5 处调用(find_marketplace_plugin_supports_alternate_layout_and_string_local_source, list_marketplaces_includes_plugins_without_discoverable_manifest, list_marketplaces_keeps_remote_and_local_plugin_sources, list_marketplaces_prefers_first_supported_manifest_layout, list_marketplaces_supports_alternate_manifest_layout);外部调用 3 个(join, create_dir_all, write)。

write_alternate_plugin_manifest17–21 ↗
fn write_alternate_plugin_manifest(plugin_root: &Path, contents: &str)

作用:这个小工具函数用来给某个插件写备用布局的 plugin.json。它让测试可以模拟插件自己带有展示信息,比如显示名。

数据流:输入插件根目录和 JSON 内容 → 它拼出 .claude-plugin/plugin.json,创建目录并写入文件 → 不返回数据,但磁盘上多了一个测试用插件清单。

调用关系:它只被备用清单布局的测试使用。测试先用它放好插件清单,再用市场列表读取流程确认这些界面信息能被读出来。

调用图:被 1 处调用(list_marketplaces_supports_alternate_manifest_layout);外部调用 3 个(join, create_dir_all, write)。

find_marketplace_plugin_finds_repo_marketplace_plugin24–70 ↗
fn find_marketplace_plugin_finds_repo_marketplace_plugin()

作用:这个测试确认:仓库里的标准位置 marketplace.json 能被读取,并且本地插件路径能被正确解析。没有这个保证,最常见的仓库内插件市场就可能失效。

数据流:测试先创建临时仓库、.git 目录、市场文件和一个本地插件声明 → 调用查找插件的能力 → 期望得到插件 ID、本地绝对路径、默认安装策略等完整结果。

调用关系:测试运行器会执行它。它通过临时文件系统搭场景,最后用断言检查真正的插件查找流程有没有按标准仓库布局工作。

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

find_marketplace_plugin_supports_alternate_layout_and_string_local_source73–113 ↗
fn find_marketplace_plugin_supports_alternate_layout_and_string_local_source()

作用:这个测试确认备用市场布局也能用,而且 source 直接写成字符串路径时也能识别成本地插件。这样旧格式或兼容格式不会突然坏掉。

数据流:测试创建临时仓库,用 write_alternate_marketplace 写入 .claude-plugin/marketplace.json → 查找指定插件 → 期望插件来源被解析成仓库内 plugins/string-source-plugin 的绝对路径。

调用关系:测试运行器调用它;它依赖 write_alternate_marketplace 先准备文件,然后检查插件查找流程对备用布局和简写 source 的兼容性。

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

find_marketplace_plugin_supports_git_subdir_sources116–167 ↗
fn find_marketplace_plugin_supports_git_subdir_sources()

作用:这个测试确认插件可以来自 Git 仓库里的某个子目录。很多插件不是整个仓库,而是仓库中的一个文件夹,这个规则很重要。

数据流:测试写入一个 git-subdir 来源,里面有 GitHub 简写地址、子目录、分支名和提交哈希 → 查找插件 → 期望结果变成完整 Git 地址,并保留子目录、ref 和 sha 信息。

调用关系:测试运行器执行它。它验证市场解析流程能把远程 Git 子目录插件转换成安装器后续能理解的结构。

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

find_marketplace_plugin_normalizes_github_shorthand_with_dot_git_suffix170–208 ↗
fn find_marketplace_plugin_normalizes_github_shorthand_with_dot_git_suffix()

作用:这个测试确认类似 openai/toolkit.git 这样的 GitHub 简写会被补成完整网址,而且不会重复加 .git。它防止生成错误的仓库地址。

数据流:测试写入一个带 .git 后缀的 GitHub 简写来源 → 查找插件 → 只检查解析出的 source,期望 URL 是 https://github.com/openai/toolkit.git。

调用关系:测试运行器调用它。它专门盯住 Git 地址标准化这一步,确保后续克隆代码时拿到的是正确地址。

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

find_marketplace_plugin_normalizes_relative_git_source_urls_to_marketplace_root211–256 ↗
fn find_marketplace_plugin_normalizes_relative_git_source_urls_to_marketplace_root()

作用:这个测试确认相对 Git 地址会相对于市场所在仓库来解释,而不是相对于当前运行目录乱跳。这样本地测试仓库或本地镜像地址才可靠。

数据流:测试分别用 Unix 风格和 Windows 风格的相对路径写入 Git 来源 → 创建对应的本地远程仓库目录 → 查找插件 → 期望 Git URL 被解析成市场根目录下 remotes/toolkit.git 的路径字符串。

调用关系:测试运行器执行它。它覆盖跨平台路径写法,验证相对 Git 地址归一化这段逻辑不会因为斜杠不同而出错。

调用图:调用 1 个内部函数(try_from);外部调用 5 个(assert_eq!, format!, create_dir_all, write, tempdir)。

normalize_relative_git_plugin_source_url_rejects_parent_traversal259–281 ↗
fn normalize_relative_git_plugin_source_url_rejects_parent_traversal()

作用:这个测试确认相对 Git 地址不能用 .. 跑到市场根目录外面。.. 就像文件路径里的“上一层”,如果不拦住,配置文件可能引用不该碰的地方。

数据流:测试把多种向上跳目录的路径交给相对 Git URL 规范化函数 → 函数返回错误 → 测试确认错误信息明确说明相对地址必须留在市场根目录内。

调用关系:测试运行器调用它。它直接检查路径安全规则,是插件市场防止目录逃逸的一道测试。

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

find_marketplace_plugin_skips_root_equivalent_git_subdir_paths284–321 ↗
fn find_marketplace_plugin_skips_root_equivalent_git_subdir_paths()

作用:这个测试确认 git-subdir 的 path 如果等价于仓库根目录,就不会被当成有效插件子目录。它避免“子目录插件”规则被空路径或绕法混淆。

数据流:测试用 .、./、plugins/.. 这些最终等于根目录的 path 写市场文件 → 查找插件 → 期望找不到这个插件,而不是返回一个根目录来源。

调用关系:测试运行器执行它。它把几个容易绕过检查的路径写法喂给插件查找流程,确认无效条目会被跳过。

调用图:调用 1 个内部函数(try_from);外部调用 5 个(assert_eq!, format!, create_dir_all, write, tempdir)。

find_marketplace_plugin_reports_missing_plugin324–345 ↗
fn find_marketplace_plugin_reports_missing_plugin()

作用:这个测试确认市场里没有目标插件时,会给出清楚的“找不到”错误。这样用户或上层代码不会误以为是文件坏了。

数据流:测试写入一个没有插件的市场文件 → 查找名为 missing 的插件 → 期望返回错误,错误里带插件名和市场名。

调用关系:测试运行器调用它。它检查插件查找流程的失败提示是否友好、准确。

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

list_marketplaces_supports_alternate_manifest_layout348–421 ↗
fn list_marketplaces_supports_alternate_manifest_layout()

作用:这个测试确认列出市场时,也能读取备用布局里的市场文件和插件清单。它保证兼容格式不仅查找能用,列表展示也能用。

数据流:测试创建备用 marketplace.json 和备用 plugin.json,插件清单里有 displayName → 调用列市场流程 → 期望返回一个市场,其中插件带本地路径和读取到的显示名。

调用关系:测试运行器执行它。它先借助 write_alternate_marketplace 和 write_alternate_plugin_manifest 准备场景,再验证完整的市场列表流程。

调用图:调用 3 个内部函数(write_alternate_marketplace, write_alternate_plugin_manifest, try_from);外部调用 3 个(assert_eq!, create_dir_all, tempdir)。

list_marketplaces_includes_plugins_without_discoverable_manifest424–472 ↗
fn list_marketplaces_includes_plugins_without_discoverable_manifest()

作用:这个测试确认插件就算没有能找到的 plugin.json,也仍然会出现在市场列表里。这样一个缺少展示信息的插件不会被整个隐藏。

数据流:测试写入一个指向不存在插件目录的市场条目 → 调用列市场流程 → 期望插件仍在列表中,只是 interface 这些展示信息为空。

调用关系:测试运行器调用它。它使用 write_alternate_marketplace 准备市场文件,验证列表流程对缺失插件清单采取宽容处理。

调用图:调用 2 个内部函数(write_alternate_marketplace, try_from);外部调用 3 个(assert_eq!, create_dir_all, tempdir)。

list_marketplaces_prefers_first_supported_manifest_layout475–523 ↗
fn list_marketplaces_prefers_first_supported_manifest_layout()

作用:这个测试确认同一个仓库里同时存在多个支持的市场布局时,会优先使用第一个约定布局。这样结果稳定,不会一会儿读这个一会儿读那个。

数据流:测试同时写入标准位置和备用位置的市场文件 → 调用列市场流程 → 期望只返回标准位置的 agents-marketplace。

调用关系:测试运行器执行它。它用 write_alternate_marketplace 放备用文件,同时手写标准文件,检查市场发现顺序是否符合约定。

调用图:调用 2 个内部函数(write_alternate_marketplace, try_from);外部调用 4 个(assert_eq!, create_dir_all, write, tempdir)。

list_marketplaces_supports_explicit_api_marketplace_manifest_path526–579 ↗
fn list_marketplaces_supports_explicit_api_marketplace_manifest_path()

作用:这个测试确认如果调用方直接给的是某个 marketplace.json 文件路径,而不是仓库目录,也能正常读取。API 场景常会这样传。

数据流:测试创建一个明确命名的 api_marketplace.json → 把这个文件路径作为输入传给列市场流程 → 期望返回这个市场和其中的本地插件。

调用关系:测试运行器调用它。它检查市场列表入口不仅能发现仓库里的默认文件,也能尊重调用方指定的具体文件。

调用图:调用 1 个内部函数(try_from);外部调用 5 个(assert_eq!, create_dir_all, write, from_ref, tempdir)。

list_marketplaces_returns_home_and_repo_marketplaces582–723 ↗
fn list_marketplaces_returns_home_and_repo_marketplaces()

作用:这个测试确认用户主目录里的市场和当前仓库里的市场会一起返回。这样全局插件和项目专属插件可以同时出现。

数据流:测试分别在 home 和 repo 下写同名市场,各自包含共享插件和独有插件 → 调用列市场流程并传入 home_dir → 期望结果按 home 市场、repo 市场两个条目返回,路径各自解析到自己的根目录。

调用关系:测试运行器执行它。它验证市场列表流程会合并多个来源,但不会把不同位置的市场混成一个。

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

list_marketplaces_keeps_distinct_entries_for_same_name726–833 ↗
fn list_marketplaces_keeps_distinct_entries_for_same_name()

作用:这个测试确认两个市场即使名字一样,只要文件位置不同,也会作为两个独立条目保留。否则 home 和 repo 的同名市场可能互相覆盖。

数据流:测试在 home 和 repo 写入同名市场,同名插件分别指向不同路径 → 列市场时期望得到两个市场 → 再按 repo 的市场文件查找插件,确认拿到 repo 版本的路径。

调用关系:测试运行器调用它。它既检查列表流程保留同名市场,也检查查找流程会根据传入的具体市场文件定位正确插件。

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

list_marketplaces_dedupes_multiple_roots_in_same_repo836–894 ↗
fn list_marketplaces_dedupes_multiple_roots_in_same_repo()

作用:这个测试确认如果输入了同一个 Git 仓库里的多个目录,只会列一次市场。这样从仓库根目录和子目录同时启动时不会重复显示同一个市场。

数据流:测试创建仓库根目录和嵌套目录,并只在仓库根目录放一个市场文件 → 把两个目录都传给列市场流程 → 期望结果只有一个市场。

调用关系:测试运行器执行它。它检查市场发现流程会先识别共同的仓库根,再去重。

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

list_marketplaces_reads_marketplace_display_name897–936 ↗
fn list_marketplaces_reads_marketplace_display_name()

作用:这个测试确认市场文件里的 interface.displayName 会被读出来。这个字段通常用于界面上显示更友好的市场名称。

数据流:测试写入带 displayName 的 marketplace.json → 调用列市场流程 → 期望返回的 MarketplaceInterface 里包含 ChatGPT Official。

调用关系:测试运行器调用它。它关注市场本身的展示信息,不是插件的展示信息。

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

list_marketplaces_skips_invalid_plugins_but_keeps_marketplace939–995 ↗
fn list_marketplaces_skips_invalid_plugins_but_keeps_marketplace()

作用:这个测试确认市场里某个插件配置无效时,市场本身仍会保留,只是跳过坏插件。这样一个坏条目不会让整个市场消失。

数据流:测试准备一个有效市场和一个含无效本地路径的市场 → 调用列市场流程 → 期望两个市场都返回,但无效市场的 plugins 列表为空。

调用关系:测试运行器执行它。它检查列表流程的容错策略:市场文件能读就保留,单个插件坏了就跳过。

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

list_marketplaces_skips_plugins_with_invalid_names_but_keeps_marketplace998–1058 ↗
fn list_marketplaces_skips_plugins_with_invalid_names_but_keeps_marketplace()

作用:这个测试确认插件名不符合规则时,只跳过这个插件,不丢掉整个市场。比如带点号的名字在这里被视为无效。

数据流:测试写入一个市场,里面有 valid-plugin 和 invalid.plugin → 调用列市场流程 → 期望只剩有效插件,市场名称和路径仍正常。

调用关系:测试运行器调用它。它验证插件名校验失败时的处理边界,避免坏名字影响其他正常插件。

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

list_marketplaces_reports_marketplace_load_errors1061–1111 ↗
fn list_marketplaces_reports_marketplace_load_errors()

作用:这个测试确认市场文件本身读不懂时,错误会被收集报告,同时其他正常市场还能返回。这样界面可以展示部分结果并提示哪里坏了。

数据流:测试准备一个合法 JSON 市场和一个内容是 {not json 的坏市场 → 调用列市场流程 → 期望返回一个正常市场,并在 errors 里记录坏文件路径和错误消息。

调用关系:测试运行器执行它。它检查市场加载层的错误汇报方式,确保不是遇到第一个坏文件就全盘失败。

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

list_marketplaces_keeps_remote_and_local_plugin_sources1114–1211 ↗
fn list_marketplaces_keeps_remote_and_local_plugin_sources()

作用:这个测试确认列表结果能同时保留本地插件、普通远程 Git 插件、Git 子目录插件三种来源。插件市场现实中会混用这些来源。

数据流:测试写入一个包含三种 source 的市场文件 → 调用列市场流程 → 期望本地路径变成绝对路径,GitHub 地址补成完整 .git URL,子目录、ref、sha 也被保留。

调用关系:测试运行器调用它。它借助 write_alternate_marketplace 快速造市场,集中检查来源解析规则是否在列表流程里全部生效。

调用图:调用 2 个内部函数(write_alternate_marketplace, try_from);外部调用 3 个(assert_eq!, create_dir_all, tempdir)。

list_marketplaces_resolves_plugin_interface_paths_to_absolute1214–1301 ↗
fn list_marketplaces_resolves_plugin_interface_paths_to_absolute()

作用:这个测试确认插件清单里的图标、logo、截图这些相对资源路径,会被解析成绝对路径。这样后续界面加载图片时不用猜它们相对于哪里。

数据流:测试创建本地插件和 .codex-plugin/plugin.json,里面有显示名、分类、能力和 ./assets 开头的图片路径 → 调用列市场流程 → 期望界面信息被合并,图片路径都变成插件目录下的绝对路径,市场里的分类覆盖清单里的分类。

调用关系:测试运行器执行它。它验证市场列表流程会读取插件清单,并把展示资源路径整理成安全、明确的位置。

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

list_marketplaces_ignores_legacy_top_level_policy_fields1304–1345 ↗
fn list_marketplaces_ignores_legacy_top_level_policy_fields()

作用:这个测试确认旧的顶层 installPolicy 和 authPolicy 字段会被忽略,仍使用新的默认策略。这样旧字段不会偷偷改变当前安装规则。

数据流:测试写入含旧字段的插件声明,其中旧字段表示不可安装、使用时认证 → 调用列市场流程 → 期望实际策略仍是默认的 Available 和 OnInstall,products 为空。

调用关系:测试运行器调用它。它盯住配置兼容边界,确保只有当前支持的 policy 结构会影响安装策略。

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

list_marketplaces_ignores_plugin_interface_assets_without_dot_slash1348–1422 ↗
fn list_marketplaces_ignores_plugin_interface_assets_without_dot_slash()

作用:这个测试确认插件界面资源路径必须用 ./ 开头才会被接受。没有 ./ 的相对路径或绝对路径会被忽略,避免随便引用外部文件。

数据流:测试写入插件清单,其中 composerIcon 是 assets/icon.png,logo 是 /tmp/logo.png,截图也是不带 ./ 的路径 → 调用列市场流程 → 期望文字信息保留,但这些资源路径全部为空。

调用关系:测试运行器执行它。它检查插件清单资源路径的安全过滤规则,和默认安装策略是否仍保持正常。

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

find_marketplace_plugin_skips_invalid_local_paths1425–1457 ↗
fn find_marketplace_plugin_skips_invalid_local_paths()

作用:这个测试确认本地插件路径不能用 ../ 指到市场目录外。这样的路径会被视为无效,插件查找结果等同于找不到。

数据流:测试写入一个本地 source,路径是 ../plugin-1 → 查找 local-plugin → 期望返回“插件未找到”的错误,而不是暴露或使用外部路径。

调用关系:测试运行器调用它。它验证插件查找流程里的本地路径安全限制。

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

find_marketplace_plugin_uses_first_duplicate_entry1460–1501 ↗
fn find_marketplace_plugin_uses_first_duplicate_entry()

作用:这个测试确认市场里有两个同名插件时,会使用第一个条目。这个规则让重复配置的结果可预测。

数据流:测试写入两个都叫 local-plugin 的条目,第一个指向 ./first,第二个指向 ./second → 查找插件 → 期望结果使用 first 的路径。

调用关系:测试运行器执行它。它检查插件查找流程遇到重复名称时不会随机选择,而是按文件顺序取第一个有效项。

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

find_installable_marketplace_plugin_rejects_disallowed_product1504–1540 ↗
fn find_installable_marketplace_plugin_rejects_disallowed_product()

作用:这个测试确认插件如果只允许某些产品安装,就不能被其他产品安装。这里 Product 指不同宿主产品,比如 Codex、ChatGPT、Atlas。

数据流:测试写入一个只允许 CHATGPT 的插件 → 以 Atlas 产品身份请求查找可安装插件 → 期望返回“不可安装”的错误。

调用关系:测试运行器调用它。它验证可安装插件查找流程会在普通查找之外,再检查产品白名单。

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

find_marketplace_plugin_allows_missing_products_field1543–1573 ↗
fn find_marketplace_plugin_allows_missing_products_field()

作用:这个测试确认 policy 里没有 products 字段时,不会因此拒绝插件。缺省值表示没有额外产品限制。

数据流:测试写入一个 policy 为空对象的插件 → 普通查找插件 → 期望成功,并得到 default-plugin@codex-curated 这个插件键。

调用关系:测试运行器执行它。它检查普通插件查找流程对缺省产品字段的宽容行为。

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

find_installable_marketplace_plugin_rejects_explicit_empty_products1576–1612 ↗
fn find_installable_marketplace_plugin_rejects_explicit_empty_products()

作用:这个测试确认 products 明确写成空数组时,表示没有任何产品可以安装。它和“字段缺失”不同。

数据流:测试写入一个 products: [] 的插件 → 以 Codex 产品身份请求可安装插件 → 期望返回不可安装错误。

调用关系:测试运行器调用它。它检查可安装查找流程能区分“没写限制”和“明确禁止所有产品”。

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

core-plugins/src/remote_tests.rs源码 ↗
testtest

这个文件不提供正式功能,而是专门用来抓错误。项目里会从远程接口拿到插件列表,再把它们变成用户能看到、能安装的插件数据。这里的测试会模拟各种远程返回:有正常插件、重复插件、被管理员禁用的插件、名字太长的插件、缺少安装身份的插件等。然后它检查系统是否做了正确决定,比如:远程市场要保留目录里的顺序,同时把“只在本地已安装”的插件追加到最后;推荐插件只有在接口明确说 enabled=true 时才走新模式;无效或不可安装的推荐插件要被丢掉;最终推荐列表要排序、去重,并限制最大数量。文件里还有两个小工厂函数,专门快速造测试用插件,避免每个测试都写一大坨重复数据。

函数细节9
build_remote_marketplace_preserves_directory_order_and_appends_installed_only_plugins5–34 ↗
fn build_remote_marketplace_preserves_directory_order_and_appends_installed_only_plugins()

作用:这个测试确认远程插件市场的展示顺序是稳定的:远程目录给什么顺序,就先按这个顺序显示;如果某个插件只存在于已安装列表里,也不会丢掉,而是追加到最后。

数据流:进去的是两个模拟列表:一个远程目录列表,里面有 plugin-z 和 plugin-m;一个已安装列表,里面有 plugin-a。测试把这些交给 build_remote_marketplace,拿回生成后的市场结果,再抽出每个插件的远程插件 ID。最后它断言结果顺序必须是 plugin-z、plugin-m、plugin-a,说明目录顺序被保住了,额外已安装插件也被补上了。

调用关系:这是直接验证 build_remote_marketplace 行为的测试。它自己准备测试数据,其中插件对象来自 directory_plugin;最后用 assert_eq! 做比对,确保未来有人改市场合并逻辑时不会无意打乱顺序。

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

directory_plugin36–78 ↗
fn directory_plugin(id: &str, name: &str) -> RemotePluginDirectoryItem

作用:这是一个测试辅助函数,用来快速造一个“远程目录里的插件”假数据。有人写测试时只需要给插件 ID 和名字,就能得到一个字段齐全、默认可用的插件对象。

数据流:进去的是 id 和 name 两个字符串。函数把它们复制成真正拥有数据的字符串,并填入 RemotePluginDirectoryItem 的各个字段;大部分不重要的字段会设成空值、空列表或默认可用状态。出来的是一个完整的远程目录插件对象,可直接塞进测试里的市场构建流程。

调用关系:它是测试里的造假数据工具,主要服务于远程市场相关测试。build_remote_marketplace_preserves_directory_order_and_appends_installed_only_plugins 用它快速生成目录插件和已安装插件里的 plugin 字段,避免测试正文被大量结构体字段淹没。

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

item79–90 ↗
fn item(name: &str, display_name: &str) -> RecommendedPluginItem

作用:这是一个测试辅助函数,用来快速造一个“推荐插件接口返回的插件条目”。它让测试只关注插件名字和展示名,而不用反复手写完整结构。

数据流:进去的是 name 和 display_name。函数会把 name 变成插件安装用的 id,比如 plugin_github,并填入推荐插件条目的名字、展示名和空的应用连接器列表。出来的是一个 RecommendedPluginItem,供推荐插件规则测试继续加工或混入异常数据。

调用关系:它被 recommended_plugins_are_validated_deduplicated_sorted_and_capped 调用,用来批量生成一堆推荐插件。它把重复的造数工作封装起来,让那个测试能专心检查排序、去重和数量上限。

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

插件生命周期与发现流程

本组从启动同步和远程共享,推进到管理器级编排、可发现性、路由,以及面向核心的插件适配器和提及/渲染行为。

core-plugins/src/startup_sync_tests.rs源码 ↗
testtest

这份文件不是线上功能本身,而是给同步插件功能做“压力体检”的测试。项目启动时需要把官方精选插件下载到用户的 codex home 目录下,如果 Git 不可用、网络返回坏压缩包、两次同步同时发生,或者新下载内容不合格,都可能把本地插件弄丢。这里用临时目录当假用户目录,用 wiremock(一个假 HTTP 服务器)冒充 GitHub,用小脚本冒充 git 命令,再检查结果是不是符合预期。它还会造出测试用插件清单、插件 manifest、zip 压缩包和备份归档。重点不是测“能不能下载一次”,而是测:失败时要清理临时目录,已有可用快照不能被坏更新覆盖,版本号 SHA 要正确保存,并发时要排队,避免两个同步互相踩脚。

函数细节31
write_file19–22 ↗
fn write_file(path: &Path, contents: &str)

作用:这是测试里的小工具,用来把一段文字写进指定文件。它会先把父目录建好,避免测试因为目录不存在而失败。

数据流:输入一个文件路径和文本内容 → 它找到这个文件的上级目录并创建出来 → 再把文本写入文件;结果是磁盘上出现这个测试需要的文件。

调用关系:它是造测试数据的底层工具。write_curated_plugin、write_curated_plugin_sha 和 write_openai_curated_marketplace 都把具体内容准备好后,交给它真正落到磁盘。

调用图:被 3 处调用(write_curated_plugin, write_curated_plugin_sha, write_openai_curated_marketplace);外部调用 3 个(parent, create_dir_all, write)。

write_curated_plugin24–30 ↗
fn write_curated_plugin(root: &Path, plugin_name: &str)

作用:这个函数造一个假的精选插件目录。它写出插件自己的 plugin.json,让测试里的仓库看起来像真的有一个插件。

数据流:输入测试仓库根目录和插件名 → 拼出 plugins/<插件名>/.codex-plugin/plugin.json 这个位置 → 写入只包含插件名的 JSON;结果是一个最小可识别的插件 manifest 文件。

调用关系:它服务于 write_openai_curated_marketplace。后者先写总市场清单,再调用它为清单里的每个插件补上对应的插件文件。

调用图:调用 1 个内部函数(write_file);被 1 处调用(write_openai_curated_marketplace);外部调用 2 个(join, format!)。

write_openai_curated_marketplace32–62 ↗
fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str])

作用:这个函数造一个假的 OpenAI 官方精选插件市场。测试用它快速搭出“有 gmail、linear 等插件”的本地仓库。

数据流:输入仓库根目录和一组插件名 → 生成 marketplace.json,里面列出每个插件和它的本地路径 → 再逐个创建对应插件 manifest;结果是一个结构完整的假精选插件仓库。

调用关系:多个测试用它准备已有快照或更新后的仓库。它内部把写文件交给 write_file,把每个插件目录交给 write_curated_plugin。

调用图:调用 2 个内部函数(write_curated_plugin, write_file);被 3 处调用(sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists, sync_openai_plugins_repo_via_git_preserves_existing_snapshot_on_validation_failure, sync_openai_plugins_repo_via_git_succeeds_with_local_rewritten_remote);外部调用 2 个(join, format!)。

write_curated_plugin_sha64–69 ↗
fn write_curated_plugin_sha(codex_home: &Path)

作用:这个函数写入一个固定的测试用插件版本号。这里的 SHA 可以理解成“仓库版本身份证”。

数据流:输入 codex home 路径 → 在 .tmp/plugins.sha 写入固定 SHA 和换行 → 后续读取时应该能知道当前本地快照对应哪个版本。

调用关系:一些测试先用它伪造“本地已经同步过某个版本”。它把实际写入动作交给 write_file。

调用图:调用 1 个内部函数(write_file);被 2 处调用(sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists, sync_openai_plugins_repo_via_git_preserves_existing_snapshot_on_validation_failure);外部调用 2 个(join, format!)。

has_plugins_clone_dirs71–84 ↗
fn has_plugins_clone_dirs(codex_home: &Path) -> bool

作用:这个函数检查临时目录里是否还残留 plugins-clone- 开头的同步临时文件夹。它用来确认失败或成功后有没有把施工现场打扫干净。

数据流:输入 codex home 路径 → 读取 .tmp 目录 → 查找名字以 plugins-clone- 开头且确实是目录的项目;输出 true 或 false。

调用关系:它是测试断言用的检查器。同步代码会创建临时克隆目录,这个函数帮助测试确认这些目录没有在异常路径里泄漏。

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

write_executable_script87–98 ↗
fn write_executable_script(path: &Path, contents: &str)

作用:这个 Unix 专用工具会写出一个可执行脚本。测试用它伪造 git 命令,让同步流程以为自己在调用真实 git。

数据流:输入脚本路径和脚本文本 → 把内容写到文件 → 再把文件权限改成可执行;结果是测试可以直接运行这个脚本。

调用关系:需要模拟 git 成功、失败、卡顿或记录调用次数的测试都会用它。它让测试不用依赖真实网络上的 GitHub 行为。

调用图:被 5 处调用(concurrent_syncs_serialize_fetches_without_skipping_remote_checks, sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails, sync_openai_plugins_repo_via_git_cleans_up_staged_dir_on_fetch_failure, sync_openai_plugins_repo_via_git_preserves_existing_snapshot_on_validation_failure, sync_openai_plugins_repo_via_git_succeeds_with_local_rewritten_remote);外部调用 3 个(metadata, set_permissions, write)。

run_git101–114 ↗
fn run_git(repo: &Path, args: &[&str]) -> std::process::Output

作用:这个 Unix 专用工具在指定仓库里运行真实 git 命令,并要求命令必须成功。它让测试可以搭一个真正的本地 Git 仓库。

数据流:输入仓库路径和 git 参数 → 执行 git -C <仓库> ... → 如果失败就让测试立刻报错;如果成功就返回命令输出。

调用关系:主要服务于 sync_openai_plugins_repo_via_git_succeeds_with_local_rewritten_remote。那个测试用它初始化仓库、提交、读取 SHA 和推送更新。

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

mount_github_repo_and_ref116–130 ↗
async fn mount_github_repo_and_ref(server: &MockServer, sha: &str)

作用:这个异步函数在假 HTTP 服务器上挂两个 GitHub 接口:一个说默认分支是 main,一个返回 main 分支的 SHA。

数据流:输入假服务器和目标 SHA → 注册 GET /repos/openai/plugins 和 GET /repos/openai/plugins/git/ref/heads/main 的假响应 → 后续同步代码访问这些地址时会拿到预设版本信息。

调用关系:HTTP 同步相关测试先调用它搭好“GitHub 元信息”。它和 mount_github_zipball 常常配套,一个给版本号,一个给压缩包内容。

调用图:被 4 处调用(sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable, sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails, sync_openai_plugins_repo_skips_archive_download_when_sha_matches, sync_openai_plugins_repo_via_http_cleans_up_staged_dir_on_extract_failure);外部调用 5 个(given, new, format!, method, path)。

mount_github_zipball132–142 ↗
async fn mount_github_zipball(server: &MockServer, sha: &str, bytes: Vec<u8>)

作用:这个异步函数让假服务器提供某个 SHA 对应的 zip 压缩包。同步代码会把它当作 GitHub 仓库下载包。

数据流:输入假服务器、SHA 和 zip 字节 → 注册 GET /repos/openai/plugins/zipball/<sha> → 返回 content-type 为 application/zip 的字节内容。

调用关系:当测试需要走 HTTP 下载路径时,它跟 mount_github_repo_and_ref 一起布置假 GitHub。压缩包内容通常来自 curated_repo_zipball_bytes。

调用图:被 3 处调用(sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable, sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails, sync_openai_plugins_repo_via_http_cleans_up_staged_dir_on_extract_failure);外部调用 5 个(given, new, format!, method, path)。

mount_export_archive144–164 ↗
async fn mount_export_archive(server: &MockServer, bytes: Vec<u8>) -> String

作用:这个异步函数搭出备份归档下载流程。它模拟一个后端接口先返回下载地址,再由另一个地址返回 zip 文件。

数据流:输入假服务器和归档 zip 字节 → 注册 /backend-api/plugins/export/curated 返回 download_url → 再注册 /files/curated-plugins.zip 返回真正 zip;输出第一个接口的 URL。

调用关系:测试 GitHub 路径失败时的最后兜底方案会调用它。归档内容一般由 curated_repo_backup_archive_zip_bytes 生成。

调用图:被 2 处调用(sync_openai_plugins_repo_falls_back_to_export_archive_when_no_snapshot_exists, sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists);外部调用 5 个(given, new, format!, method, path)。

run_sync_with_transport_overrides166–185 ↗
async fn run_sync_with_transport_overrides(
    codex_home: PathBuf,
    git_binary: impl Into<String>,
    api_base_url: impl Into<String>,
    backup_archive_api_url: impl Into<String>,
) -> Result<

作用:这个异步辅助函数用指定的 git 命令路径、GitHub 地址和备份地址来跑一次完整同步。它让测试可以把真实外部依赖全部换成假的。

数据流:输入 codex home、git 可执行文件名、API 基础地址和备份归档地址 → 在线程池里运行同步函数,避免阻塞异步测试 → 返回同步得到的 SHA,或返回错误文字。

调用关系:多个异步测试通过它驱动完整同步流程。它把真正的同步工作交给 sync_openai_plugins_repo_with_transport_overrides。

调用图:被 5 处调用(sync_openai_plugins_repo_falls_back_to_export_archive_when_no_snapshot_exists, sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable, sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails, sync_openai_plugins_repo_skips_archive_download_when_sha_matches, sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists);外部调用 2 个(into, spawn_blocking)。

run_http_sync187–197 ↗
async fn run_http_sync(
    codex_home: PathBuf,
    api_base_url: impl Into<String>,
) -> Result<String, String>

作用:这个异步辅助函数只跑 HTTP 方式的插件同步。它用于专门测试不用 git、只下载 zip 的那条路。

数据流:输入 codex home 和 API 基础地址 → 在线程池里调用 HTTP 同步函数 → 返回成功的 SHA 或失败原因。

调用关系:sync_openai_plugins_repo_via_http_cleans_up_staged_dir_on_extract_failure 用它触发坏 zip 的错误场景,确认临时目录会被清理。

调用图:被 1 处调用(sync_openai_plugins_repo_via_http_cleans_up_staged_dir_on_extract_failure);外部调用 2 个(into, spawn_blocking)。

assert_curated_gmail_repo199–206 ↗
fn assert_curated_gmail_repo(repo_path: &Path)

作用:这个断言函数检查一个同步后的仓库里有没有市场清单和 gmail 插件 manifest。它是“同步结果看起来像个有效仓库”的快速验收。

数据流:输入仓库路径 → 检查 .agents/plugins/marketplace.json 是否存在 → 再检查 plugins/gmail/.codex-plugin/plugin.json 是否存在;任一缺失就让测试失败。

调用关系:很多成功路径测试最后都会调用它。它不负责同步,只负责像验收员一样确认关键文件到位。

调用图:被 6 处调用(concurrent_syncs_serialize_fetches_without_skipping_remote_checks, sync_openai_plugins_repo_falls_back_to_export_archive_when_no_snapshot_exists, sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable, sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails, sync_openai_plugins_repo_via_git_preserves_existing_snapshot_on_validation_failure, sync_openai_plugins_repo_via_git_succeeds_with_local_rewritten_remote);外部调用 1 个(assert!)。

curated_plugins_repo_path_uses_codex_home_tmp_dir209–215 ↗
fn curated_plugins_repo_path_uses_codex_home_tmp_dir()

作用:这个测试确认精选插件仓库会放在 codex home 的 .tmp/plugins 下面。路径放对很重要,否则可能污染用户别的目录。

数据流:创建临时 codex home → 调用路径计算函数 → 比较结果是否等于 <临时目录>/.tmp/plugins;测试只产生临时目录,不保留状态。

调用关系:它直接验证 curated_plugins_repo_path 的约定。这个约定会被后面几乎所有同步测试默认依赖。

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

read_curated_plugins_sha_reads_trimmed_sha_file218–227 ↗
fn read_curated_plugins_sha_reads_trimmed_sha_file()

作用:这个测试确认读取插件 SHA 时会去掉末尾换行。版本文件通常有换行,读取出来不该把换行当成版本号的一部分。

数据流:创建 .tmp/plugins.sha,写入 abc123 加换行 → 调用读取函数 → 期望得到 Some("abc123")。

调用关系:它验证 read_curated_plugins_sha 的基础行为。后续跳过下载、判断版本是否相同都依赖这个读取结果准确。

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

remove_stale_curated_repo_temp_dirs_removes_only_matching_directories231–270 ↗
fn remove_stale_curated_repo_temp_dirs_removes_only_matching_directories()

作用:这个 Unix 测试确认清理旧临时目录时只删该删的。它不能误删新目录,也不能误删无关缓存。

数据流:创建一个旧的 plugins-clone 目录、一个新的 plugins-clone 目录和一个无关目录 → 手动设置修改时间 → 调用清理函数 → 断言只有过期的匹配目录被删除。

调用关系:它验证 remove_stale_curated_repo_temp_dirs。测试里还定义了一个小的 set_dir_mtime 帮手,用系统调用改目录时间。

调用图:外部调用 4 个(from_secs, assert!, create_dir_all, tempdir)。

concurrent_syncs_serialize_fetches_without_skipping_remote_checks274–367 ↗
fn concurrent_syncs_serialize_fetches_without_skipping_remote_checks()

作用:这个 Unix 测试检查两次同步同时启动时不会互相打架。它还确认每次都会检查远端版本,但真正下载只做必要的一次。

数据流:创建假 git 脚本并记录所有调用 → 用屏障让两个线程同时开始同步 → 假 git 返回同一个 SHA 并在 reset 时造出 gmail 插件 → 最后检查两个同步都成功、仓库有效、SHA 已保存、ls-remote 调了两次而 fetch 只调了一次。

调用关系:它用 write_executable_script 伪造 git,用 assert_curated_gmail_repo 验收结果。这个测试重点覆盖同步函数里的串行化保护,也就是类似“排队进门”的锁。

调用图:调用 2 个内部函数(assert_curated_gmail_repo, write_executable_script);外部调用 8 个(new, assert!, assert_eq!, format!, read_to_string, scope, new, tempdir)。

sync_openai_plugins_repo_via_git_succeeds_with_local_rewritten_remote371–579 ↗
fn sync_openai_plugins_repo_via_git_succeeds_with_local_rewritten_remote()

作用:这个 Unix 测试走一遍真实 git 同步成功路径,并检查增量更新和无变化时的行为。它把 GitHub 地址重写成本地裸仓库,所以不用访问网络。

数据流:先创建一个真实本地仓库,提交 gmail 插件 → 配置 git 把 https://github.com/ 重写到本地路径 → 运行同步并检查 SHA、文件和临时目录 → 再提交 linear 插件并推送 → 再同步,确认新插件出现且使用 fetch 更新 → 第三次同步时远端没变,确认不会再 fetch。

调用关系:它用 run_git 搭建真实仓库,用 write_executable_script 包装 git 并记录调用,用 write_openai_curated_marketplace 制造更新内容,用 assert_curated_gmail_repo 验收同步结果。

调用图:调用 4 个内部函数(assert_curated_gmail_repo, run_git, write_executable_script, write_openai_curated_marketplace);外部调用 10 个(from_utf8_lossy, assert!, assert_eq!, new, format!, create_dir_all, read_to_string, write, new, tempdir)。

sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable582–603 ↗
async fn sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable()

作用:这个异步测试确认如果 git 命令根本不存在,同步会自动改走 HTTP 下载 zip 的备用路线。

数据流:启动假服务器 → 挂上 GitHub 版本接口和 zipball 接口 → 用一个不存在的 git 名字运行同步 → 期望同步成功,返回指定 SHA,并在本地生成 gmail 插件仓库。

调用关系:它用 mount_github_repo_and_ref 和 mount_github_zipball 布置假 GitHub,用 curated_repo_zipball_bytes 生成 zip,用 run_sync_with_transport_overrides 驱动完整流程。

调用图:调用 5 个内部函数(assert_curated_gmail_repo, curated_repo_zipball_bytes, mount_github_repo_and_ref, mount_github_zipball, run_sync_with_transport_overrides);外部调用 3 个(start, assert_eq!, tempdir)。

sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails607–641 ↗
async fn sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails()

作用:这个 Unix 异步测试确认 git 虽然存在但执行失败时,也会回退到 HTTP 下载。这样用户本机 git 配置坏了也不至于完全不能同步。

数据流:写一个永远退出失败的假 git → 启动假服务器并提供版本信息和 zip 包 → 运行同步 → 期望它忽略 git 失败,改用 HTTP 成功拿到仓库和 SHA。

调用关系:它用 write_executable_script 制造失败 git,用 mount_github_repo_and_ref、mount_github_zipball 和 curated_repo_zipball_bytes 准备 HTTP 备用内容,再通过 run_sync_with_transport_overrides 触发同步。

调用图:调用 6 个内部函数(assert_curated_gmail_repo, curated_repo_zipball_bytes, mount_github_repo_and_ref, mount_github_zipball, run_sync_with_transport_overrides, write_executable_script);外部调用 4 个(start, assert_eq!, new, tempdir)。

sync_openai_plugins_repo_via_git_cleans_up_staged_dir_on_fetch_failure645–681 ↗
fn sync_openai_plugins_repo_via_git_cleans_up_staged_dir_on_fetch_failure()

作用:这个 Unix 测试确认 git fetch 中途失败时,临时克隆目录会被删除。否则用户目录里会越积越多半成品。

数据流:写一个假 git:ls-remote 成功、init 成功、fetch 报 early EOF → 调用 git 同步 → 期望返回包含错误信息的失败结果 → 再检查没有残留 plugins-clone 临时目录。

调用关系:它用 write_executable_script 控制 git 的失败点。测试目标是 sync_openai_plugins_repo_via_git 的失败清理行为。

调用图:调用 1 个内部函数(write_executable_script);外部调用 4 个(assert!, format!, new, tempdir)。

sync_openai_plugins_repo_via_git_preserves_existing_snapshot_on_validation_failure685–752 ↗
fn sync_openai_plugins_repo_via_git_preserves_existing_snapshot_on_validation_failure()

作用:这个 Unix 测试确认新下载内容如果校验不过,旧的可用插件快照不会被覆盖。就像更新软件失败时,不能把原来能用的版本删掉。

数据流:先写入一个已有的 gmail 仓库和旧 SHA → 假 git 声称远端有新 SHA,但 reset 出来的新内容缺少市场清单 → 调用同步后期望失败 → 检查旧 gmail 仍在、linear 没被换进去、旧 SHA 仍保留、临时目录已清理。

调用关系:它用 write_openai_curated_marketplace 和 write_curated_plugin_sha 准备旧快照,用 write_executable_script 伪造坏更新,用 assert_curated_gmail_repo 确认旧内容还活着。

调用图:调用 4 个内部函数(assert_curated_gmail_repo, write_curated_plugin_sha, write_executable_script, write_openai_curated_marketplace);外部调用 6 个(assert!, assert_eq!, format!, create_dir_all, new, tempdir)。

sync_openai_plugins_repo_via_http_cleans_up_staged_dir_on_extract_failure755–769 ↗
async fn sync_openai_plugins_repo_via_http_cleans_up_staged_dir_on_extract_failure()

作用:这个异步测试确认 HTTP 下载到的内容不是合法 zip 时,同步会失败并清理临时目录。

数据流:假服务器返回正常 SHA,但 zipball 内容是普通文字 not a zip archive → 运行 HTTP 同步 → 期望错误提示说打不开 zip → 再确认没有残留临时克隆目录。

调用关系:它用 mount_github_repo_and_ref 设置版本,用 mount_github_zipball 提供坏包,用 run_http_sync 专门触发 HTTP 同步路径。

调用图:调用 3 个内部函数(mount_github_repo_and_ref, mount_github_zipball, run_http_sync);外部调用 3 个(start, assert!, tempdir)。

sync_openai_plugins_repo_skips_archive_download_when_sha_matches772–799 ↗
async fn sync_openai_plugins_repo_skips_archive_download_when_sha_matches()

作用:这个异步测试确认本地已有 SHA 和远端 SHA 一样时,不需要再下载压缩包。这样能省网络,也减少出错机会。

数据流:先在本地创建一个已有仓库和 plugins.sha → 假服务器只提供远端 SHA,不提供 zipball → 运行同步 → 如果同步成功,就说明它只检查了版本,没有尝试下载缺失的 zip。

调用关系:它用 mount_github_repo_and_ref 提供远端版本,用 run_sync_with_transport_overrides 运行带替换地址的完整同步。

调用图:调用 2 个内部函数(mount_github_repo_and_ref, run_sync_with_transport_overrides);外部调用 7 个(start, assert!, assert_eq!, format!, create_dir_all, write, tempdir)。

sync_openai_plugins_repo_falls_back_to_export_archive_when_no_snapshot_exists802–831 ↗
async fn sync_openai_plugins_repo_falls_back_to_export_archive_when_no_snapshot_exists()

作用:这个异步测试确认当 GitHub 查询失败且本地没有旧快照时,会使用后端导出的备份归档。它是最后一道兜底。

数据流:假 GitHub repo 查询返回 500 → 假后端导出接口返回一个备份 zip 下载地址 → 运行同步 → 期望从备份归档恢复出 gmail 仓库,并保存归档里的 SHA。

调用关系:它用 curated_repo_backup_archive_zip_bytes 生成带 .git 信息的备份包,用 mount_export_archive 挂到假服务器,再用 run_sync_with_transport_overrides 触发完整回退流程。

调用图:调用 4 个内部函数(assert_curated_gmail_repo, curated_repo_backup_archive_zip_bytes, mount_export_archive, run_sync_with_transport_overrides);外部调用 7 个(given, start, new, assert_eq!, tempdir, method, path)。

sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists834–875 ↗
async fn sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists()

作用:这个异步测试确认如果本地已经有可用快照,GitHub 失败时不会贸然用备份归档覆盖它。备份归档只在“完全没东西可用”时才用。

数据流:先写一个已有 linear 插件仓库和旧 SHA → 假 GitHub 返回失败,同时假后端提供另一个备份 zip → 运行同步 → 期望返回“跳过备份归档”的错误,并确认原来的插件文件和 SHA 没变。

调用关系:它用 write_openai_curated_marketplace 和 write_curated_plugin_sha 准备已有快照,用 mount_export_archive 提供本不该使用的备份包,用 run_sync_with_transport_overrides 检查策略是否保守。

调用图:调用 5 个内部函数(curated_repo_backup_archive_zip_bytes, mount_export_archive, run_sync_with_transport_overrides, write_curated_plugin_sha, write_openai_curated_marketplace);外部调用 9 个(given, start, new, assert!, assert_eq!, read_to_string, tempdir, method, path)。

read_extracted_backup_archive_git_sha_reads_head_ref_from_extracted_repo878–894 ↗
fn read_extracted_backup_archive_git_sha_reads_head_ref_from_extracted_repo()

作用:这个测试确认从解压后的备份仓库里能读出当前 Git SHA。备份包里不是直接给一个版本号,而是通过 .git/HEAD 指向分支引用。

数据流:创建 .git/HEAD,内容指向 refs/heads/main → 再创建 refs/heads/main 文件并写入 SHA → 调用读取函数 → 期望得到这个 SHA。

调用关系:它验证 read_extracted_backup_archive_git_sha 的正常路径。备份归档回退成功后需要靠这个函数知道应该保存哪个版本号。

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

read_extracted_backup_archive_git_sha_rejects_non_refs_head_target897–906 ↗
fn read_extracted_backup_archive_git_sha_rejects_non_refs_head_target()

作用:这个测试确认备份归档里的 HEAD 不能指向 refs/ 之外的位置。这样可以避免读到不该读的文件。

数据流:创建 .git/HEAD,写入 ref: HEAD → 调用读取函数 → 期望返回错误,错误里说明目标必须留在 refs/ 下。

调用关系:它验证 read_extracted_backup_archive_git_sha 的安全检查。这个检查防止恶意或损坏归档绕过正常 Git 引用目录。

调用图:外部调用 4 个(assert!, create_dir_all, write, tempdir)。

read_extracted_backup_archive_git_sha_rejects_path_traversal_ref909–919 ↗
fn read_extracted_backup_archive_git_sha_rejects_path_traversal_ref()

作用:这个测试确认 HEAD 引用里不能带 ../ 这种路径穿越写法。路径穿越就是用特殊路径跳出原本允许的目录。

数据流:创建 .git/HEAD,写入 refs/heads/../../evil → 调用读取函数 → 期望返回错误,说明路径组件不合法。

调用关系:它同样验证 read_extracted_backup_archive_git_sha 的安全边界,确保备份归档不能诱导程序访问仓库目录外的文件。

调用图:外部调用 4 个(assert!, create_dir_all, write, tempdir)。

curated_repo_zipball_bytes921–956 ↗
fn curated_repo_zipball_bytes(sha: &str) -> Vec<u8>

作用:这个函数在内存里生成一个 GitHub zipball 风格的测试压缩包。里面有市场清单和 gmail 插件 manifest。

数据流:输入 SHA → 创建一个内存 zip 写入器 → 以 openai-plugins-<sha> 为根目录写入 marketplace.json 和 plugin.json → 输出整个 zip 文件的字节数组。

调用关系:HTTP 回退成功相关测试用它提供假 GitHub zip 内容。mount_github_zipball 再把这些字节挂到假服务器上。

调用图:调用 1 个内部函数(new);被 2 处调用(sync_openai_plugins_repo_falls_back_to_http_when_git_is_unavailable, sync_openai_plugins_repo_falls_back_to_http_when_git_sync_fails);外部调用 4 个(default, new, new, format!)。

curated_repo_backup_archive_zip_bytes958–1002 ↗
fn curated_repo_backup_archive_zip_bytes(sha: &str) -> Vec<u8>

作用:这个函数在内存里生成一个备份归档格式的测试 zip。它比普通 zipball 多了 .git/HEAD 和分支引用,用来模拟可恢复 SHA 的备份包。

数据流:输入 SHA → 创建内存 zip → 写入 plugins/.git/HEAD、plugins/.git/refs/heads/main、市场清单和 gmail 插件 manifest → 输出 zip 字节数组。

调用关系:备份归档回退相关测试用它制造后端导出的压缩包。mount_export_archive 会把这些字节作为下载文件提供给同步流程。

调用图:调用 1 个内部函数(new);被 2 处调用(sync_openai_plugins_repo_falls_back_to_export_archive_when_no_snapshot_exists, sync_openai_plugins_repo_skips_export_archive_when_snapshot_exists);外部调用 4 个(default, new, new, format!)。

core-plugins/src/remote/share/tests.rs源码 ↗
testtest run

这个测试文件像一套演练剧本:先在临时目录里造一个假的插件,再启动一个假的 HTTP 服务器(HTTP 就是网页和服务之间传数据的协议),让它假装是 ChatGPT 后端。测试会检查插件是否被正确打成 tar.gz 压缩包、上传时是否带了正确的认证头、创建或更新远程插件时发出的 JSON 是否符合约定,还会确认本地保存的“远程插件 ID 对应哪个本地路径”没有丢。它还覆盖了几个容易出错的地方:压缩包太大要拒绝,插件清单文件必须在压缩包根目录,超长路径解包后也要正常,删除远程分享后本地映射也要清掉。简单说,它保证“本地插件分享给别人”这条链路从文件、网络到本地记录都可靠。

函数细节19
test_config25–29 ↗
fn test_config(server: &MockServer) -> RemotePluginServiceConfig

作用:给测试造一个远程插件服务配置,让被测代码把请求发到假的服务器,而不是发到真正的线上服务。

数据流:输入是一个假的 MockServer;函数读取它的地址,把地址后面拼上 /backend-api;输出一个 RemotePluginServiceConfig,后续网络请求都会按这个地址走。

调用关系:多个测试在开始时都会先调用它,比如创建、更新、列出、删除分享插件的测试。它只做地址准备,真正的请求由被测的分享函数发出去。

调用图:被 5 处调用(delete_remote_plugin_share_deletes_workspace_plugin, list_remote_plugin_shares_fetches_created_workspace_plugins, save_remote_plugin_share_creates_workspace_plugin, save_remote_plugin_share_updates_existing_workspace_plugin, update_remote_plugin_share_targets_updates_targets);外部调用 1 个(format!)。

test_auth31–33 ↗
fn test_auth() -> CodexAuth

作用:给测试准备一份假的登录信息,用来模拟已经登录的用户。

数据流:没有外部输入;它调用测试专用的认证创建函数;输出一个 CodexAuth,里面有固定的访问令牌和账号 ID,方便测试检查请求头是否正确。

调用关系:创建、更新、列出、删除等网络相关测试都会用它。它提供身份材料,被测函数随后把这些材料放进 HTTP 请求头里。

调用图:调用 1 个内部函数(create_dummy_chatgpt_auth_for_testing);被 5 处调用(delete_remote_plugin_share_deletes_workspace_plugin, list_remote_plugin_shares_fetches_created_workspace_plugins, save_remote_plugin_share_creates_workspace_plugin, save_remote_plugin_share_updates_existing_workspace_plugin, update_remote_plugin_share_targets_updates_targets)。

write_file35–38 ↗
fn write_file(path: &Path, contents: &str)

作用:把一段文字写进指定文件,并自动创建上级文件夹,省得每个测试重复写这些准备代码。

数据流:输入是文件路径和文件内容;它先找到父目录并创建目录,再把内容写入文件;结果是在磁盘临时目录里出现这个文件。

调用关系:它是测试造文件的小工具,被 write_test_plugin、write_plugin_share_local_path_mapping 以及几个压缩包测试调用。它不参与业务逻辑,只负责搭测试现场。

调用图:被 4 处调用(archive_plugin_for_upload_rejects_archives_over_limit, archive_plugin_for_upload_round_trips_through_plugin_bundle_archive_with_long_paths, write_plugin_share_local_path_mapping, write_test_plugin);外部调用 3 个(parent, create_dir_all, write)。

write_test_plugin40–51 ↗
fn write_test_plugin(root: &Path, plugin_name: &str) -> PathBuf

作用:快速造出一个最小可用的测试插件目录,里面有插件清单和一个示例技能文件。

数据流:输入是根目录和插件名;它在根目录下创建插件文件夹,再写入 .codex-plugin/plugin.jsonskills/example/SKILL.md;输出这个插件目录的路径。

调用关系:上传分享和压缩包相关测试都会先用它准备本地插件。它把写文件的细活交给 write_file。

调用图:调用 1 个内部函数(write_file);被 5 处调用(archive_plugin_for_upload_places_manifest_at_archive_root, archive_plugin_for_upload_rejects_archives_over_limit, archive_plugin_for_upload_round_trips_through_plugin_bundle_archive_with_long_paths, save_remote_plugin_share_creates_workspace_plugin, save_remote_plugin_share_updates_existing_workspace_plugin);外部调用 2 个(join, format!)。

write_plugin_share_local_path_mapping53–70 ↗
fn write_plugin_share_local_path_mapping(
    codex_home: &Path,
    remote_plugin_id: &str,
    plugin_path: &AbsolutePathBuf,
)

作用:在测试用的 Codex 主目录里写一份“远程插件 ID 对应本地插件路径”的记录。

数据流:输入是 Codex 主目录、远程插件 ID 和本地插件绝对路径;它把这些信息整理成 JSON 文件,写到 .tmp/plugin-share-local-paths-v1.json;结果是后续代码能像读真实本地缓存一样读到这条映射。

调用关系:列出分享和删除分享的测试会用它预先放入本地记录。它底层仍然通过 write_file 落盘。

调用图:调用 1 个内部函数(write_file);被 2 处调用(delete_remote_plugin_share_deletes_workspace_plugin, list_remote_plugin_shares_fetches_created_workspace_plugins);外部调用 2 个(join, format!)。

archive_file_entries72–89 ↗
fn archive_file_entries(archive_bytes: &[u8]) -> BTreeMap<String, Vec<u8>>

作用:把 tar.gz 压缩包里的普通文件读出来,方便测试检查上传的压缩包里到底放了哪些文件和内容。

数据流:输入是一段压缩包字节;它先用 gzip 解压,再按 tar 归档格式逐个读条目,只保留普通文件;输出一个按文件路径排序的映射,键是压缩包内路径,值是文件内容字节。

调用关系:创建分享测试用它检查实际上传的包,压缩包结构测试也用它检查清单文件和技能文件的位置。它是测试里的“开箱验货”工具。

调用图:被 2 处调用(archive_plugin_for_upload_places_manifest_at_archive_root, save_remote_plugin_share_creates_workspace_plugin);外部调用 2 个(new, new)。

remote_plugin_json91–110 ↗
fn remote_plugin_json(plugin_id: &str) -> serde_json::Value

作用:生成一份假的远程插件 JSON,用来模拟后端返回的插件资料。

数据流:输入是插件 ID;它填入固定的插件名、版本、描述、安装策略、认证策略和界面信息;输出一份 serde_json::Value,也就是测试里可直接当响应体用的 JSON 值。

调用关系:installed_remote_plugin_json 和 remote_plugin_json_with_share_url_and_principals 会在它的基础上加字段。它是构造各种远程插件响应的底座。

调用图:被 2 处调用(installed_remote_plugin_json, remote_plugin_json_with_share_url_and_principals);外部调用 1 个(json!)。

remote_plugin_json_with_share_url_and_principals112–125 ↗
fn remote_plugin_json_with_share_url_and_principals(
    plugin_id: &str,
    share_url: Option<&str>,
    share_principals: serde_json::Value,
) -> serde_json::Value

作用:在基础远程插件 JSON 上补上分享链接和分享对象,用来模拟“我创建并分享出去的插件”。

数据流:输入是插件 ID、可选分享链接、分享对象列表 JSON;它先拿 remote_plugin_json 生成基础对象,再加入 discoverability、share_url、share_principals 字段;输出扩展后的 JSON。

调用关系:它依赖 remote_plugin_json 提供基础结构,主要服务于列出远程分享插件的测试,让假服务器返回更接近真实后端的数据。

调用图:调用 1 个内部函数(remote_plugin_json);外部调用 2 个(json!, unreachable!)。

installed_remote_plugin_json127–135 ↗
fn installed_remote_plugin_json(plugin_id: &str) -> serde_json::Value

作用:生成一份“已经安装”的远程插件 JSON,用来模拟后端说某个工作区插件已安装且已启用。

数据流:输入是插件 ID;它先生成基础远程插件 JSON,再加入 enabled 为 true 和 disabled_skill_names 为空数组;输出带安装状态的 JSON。

调用关系:它建立在 remote_plugin_json 之上,给列出分享的测试提供“这个插件已经安装”的对照数据。

调用图:调用 1 个内部函数(remote_plugin_json);外部调用 2 个(json!, unreachable!)。

empty_pagination_json137–141 ↗
fn empty_pagination_json() -> serde_json::Value

作用:生成一个表示“没有下一页”的分页 JSON。

数据流:没有输入;它创建 next_page_token: null 的 JSON;输出给假的后端响应使用,表示列表已经到头。

调用关系:它常用于列表类测试的最后一页响应。被测列表函数看到这个值后,就应该停止继续请求下一页。

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

expected_plugin_interface143–163 ↗
fn expected_plugin_interface() -> PluginInterface

作用:给测试准备一份预期的插件展示信息,方便和被测代码解析出来的结果做完整比较。

数据流:没有输入;它填入插件显示名、短描述、能力列表等字段,其他没有提供的信息保持为空;输出一个 PluginInterface 结构。

调用关系:列出分享插件的测试用它和实际结果比对,确认远程 JSON 里的界面信息被正确转换成程序内部结构。

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

save_remote_plugin_share_creates_workspace_plugin166–280 ↗
async fn save_remote_plugin_share_creates_workspace_plugin()

作用:测试第一次分享本地插件时,系统是否会正确打包、申请上传地址、上传压缩包、创建远程工作区插件,并保存本地路径映射。

数据流:它先造临时 Codex 主目录和测试插件,再算出压缩包大小;随后假服务器依次准备三个响应:申请上传地址、接收 PUT 上传、创建工作区插件;调用 save_remote_plugin_share 后,检查返回的远程插件 ID 和分享链接,还检查本地映射文件与上传压缩包内容。

调用关系:这是创建分享流程的主测试。它调用 test_config、test_auth、write_test_plugin 和 archive_file_entries,真正的业务动作交给 save_remote_plugin_share,被 wiremock 假服务器验证请求是否按预期发生。

调用图:调用 5 个内部函数(archive_file_entries, test_auth, test_config, write_test_plugin, try_from);外部调用 11 个(given, start, new, new, assert_eq!, json!, vec!, body_json, header, method (+1 more))。

archive_plugin_for_upload_rejects_archives_over_limit283–298 ↗
fn archive_plugin_for_upload_rejects_archives_over_limit()

作用:测试插件压缩包超过大小限制时会被拒绝,避免上传过大的插件包。

数据流:它先造一个测试插件,再额外写入一个较大的文件;调用带大小限制的 archive_plugin_for_upload_with_limit,并把限制设得很小;结果应该返回 ArchiveTooLarge 错误,而不是生成压缩包。

调用关系:它使用 write_test_plugin 和 write_file 搭建超大插件现场。这个测试保护打包函数的安全边界,防止后续上传流程处理不该处理的大文件。

调用图:调用 2 个内部函数(write_file, write_test_plugin);外部调用 2 个(new, assert!)。

archive_plugin_for_upload_places_manifest_at_archive_root301–327 ↗
fn archive_plugin_for_upload_places_manifest_at_archive_root()

作用:测试打包后的插件清单文件是否放在压缩包根目录的正确位置。

数据流:它创建一个最小测试插件,调用 archive_plugin_for_upload 得到压缩包,再用 archive_file_entries 打开检查;最后确认包里只有插件清单和示例技能文件,而且路径和内容都正确。

调用关系:它调用 write_test_plugin 准备目录,再调用 archive_file_entries 验货。这个测试保证上传包的目录结构符合远端解读插件的要求。

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

archive_plugin_for_upload_round_trips_through_plugin_bundle_archive_with_long_paths330–355 ↗
fn archive_plugin_for_upload_round_trips_through_plugin_bundle_archive_with_long_paths()

作用:测试带很长文件路径的插件,打包后还能被插件包解包工具正确解出来。

数据流:它先造测试插件,再写入一个路径很深的技能文件;调用 archive_plugin_for_upload 打包,然后用 unpack_plugin_bundle_tar_gz 解包到另一个临时目录;最后读取文件,确认清单和长路径技能文件内容都没变。

调用关系:它把打包函数和解包函数连起来做往返测试。write_test_plugin 和 write_file 负责造数据,unpack_plugin_bundle_tar_gz 负责模拟接收端解包。

调用图:调用 3 个内部函数(unpack_plugin_bundle_tar_gz, write_file, write_test_plugin);外部调用 3 个(new, new, assert_eq!)。

save_remote_plugin_share_updates_existing_workspace_plugin358–423 ↗
async fn save_remote_plugin_share_updates_existing_workspace_plugin()

作用:测试重新分享已有远程插件时,系统是否走“更新已有插件”而不是“新建插件”。

数据流:它创建测试插件并计算压缩包大小;假服务器期待申请上传地址时带上已有 plugin_id,随后接收上传,并在指定插件地址上接收更新请求;调用 save_remote_plugin_share 后,检查返回的远程插件 ID 不变,且没有新的分享链接。

调用关系:这是更新分享流程的主测试。它复用 test_config、test_auth、write_test_plugin 准备环境,把核心动作交给 save_remote_plugin_share,并由假服务器确认请求路径和请求体正确。

调用图:调用 4 个内部函数(test_auth, test_config, write_test_plugin, try_from);外部调用 10 个(given, start, new, new, assert_eq!, default, json!, body_json, method, path)。

update_remote_plugin_share_targets_updates_targets426–517 ↗
async fn update_remote_plugin_share_targets_updates_targets()

作用:测试修改插件分享对象时,系统是否把用户、群组和工作区这些访问名单正确发给后端,并正确读回结果。

数据流:它启动假服务器,要求收到一个 PUT 请求,里面有可见性和目标列表;然后返回后端确认后的 principals 列表;调用 update_remote_plugin_share_targets 后,检查结果里用户、群组、角色和可见性都和响应一致。

调用关系:它调用 test_config 和 test_auth 准备网络地址与身份。真正的更新动作由 update_remote_plugin_share_targets 发出,wiremock 负责检查请求和提供响应。

调用图:调用 2 个内部函数(test_auth, test_config);外部调用 10 个(given, start, new, assert_eq!, json!, vec!, body_json, header, method, path)。

list_remote_plugin_shares_fetches_created_workspace_plugins520–693 ↗
async fn list_remote_plugin_shares_fetches_created_workspace_plugins()

作用:测试列出自己创建的工作区分享插件时,系统能翻页拉取、合并安装状态,并带上本地路径记录。

数据流:它先在本地写入一个远程 ID 到本地路径的映射;假服务器准备两页“我创建的插件”响应,再准备一份“已安装插件”响应;调用 list_remote_plugin_shares 后,检查结果里两个插件的分享信息、安装启用状态、界面信息和本地路径是否正确。

调用关系:这是列表流程的综合测试。它用 write_plugin_share_local_path_mapping 预置本地缓存,用 test_config 和 test_auth 指向假后端,核心工作交给 list_remote_plugin_shares。

调用图:调用 4 个内部函数(test_auth, test_config, write_plugin_share_local_path_mapping, try_from);外部调用 11 个(given, start, new, new, assert_eq!, json!, header, method, path, query_param (+1 more))。

delete_remote_plugin_share_deletes_workspace_plugin696–721 ↗
async fn delete_remote_plugin_share_deletes_workspace_plugin()

作用:测试删除远程分享插件后,本地保存的路径映射也会被清理掉。

数据流:它先写入一条本地路径映射;假服务器期待收到 DELETE 请求并返回成功;调用 delete_remote_plugin_share 后,再读取本地映射文件,确认里面已经没有这条记录。

调用关系:这是删除流程测试。它用 write_plugin_share_local_path_mapping 搭好删除前状态,用 test_config 和 test_auth 准备网络调用,真正删除动作交给 delete_remote_plugin_share。

调用图:调用 4 个内部函数(test_auth, test_config, write_plugin_share_local_path_mapping, try_from);外部调用 8 个(given, start, new, new, assert_eq!, header, method, path)。

core-plugins/src/manager_tests.rs源码 ↗
testtest

插件系统会碰到很多容易出错的事:配置里写了哪个插件、插件文件放在哪里、用户用哪种登录方式、远程插件和本地插件重名怎么办、缓存是不是过期、网络接口失败后要不要重试。这个测试文件就像一套“验收清单”。它先在临时目录里造出假的 Codex 家目录和插件文件,再调用真正的 PluginsManager,看最后加载出的技能、MCP 服务器(给模型用的外部工具服务)、App 连接器、市场列表、缓存内容是否正确。它还用 wiremock 模拟后端接口,避免真的访问网络。没有这些测试,插件管理器改动时很容易悄悄破坏安装、卸载、认证过滤、远程推荐或缓存更新这些关键流程。

函数细节92
plugins_manager_tracks_auth_mode58–77 ↗
fn plugins_manager_tracks_auth_mode()

作用:检查插件管理器能正确记住当前登录方式,并且只有登录方式真的变化时才说“变了”。登录方式会影响插件展示哪些能力,所以这里很关键。

数据流:先创建空的插件管理器 → 反复设置 API Key、ChatGPT 等认证模式并读取回来 → 验证返回值和保存状态是否符合预期;最后创建一个带初始认证模式的管理器,确认初始值也能保存。

调用关系:这是认证相关测试的入口之一,直接使用 PluginsManager 的构造和设置接口,不依赖本文件的辅助造插件函数。

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

write_auth_projection_plugin79–105 ↗
fn write_auth_projection_plugin(codex_home: &Path, name: &str, include_app: bool)

作用:在临时目录里写一个最小插件,用来测试“不同登录方式下插件能力怎么显示”。它可以同时写 MCP 配置,也可以顺手写 App 配置。

数据流:输入 Codex 家目录、插件名和是否包含 App → 创建插件目录、写 plugin.json 和 .mcp.json → 如果要求包含 App,就调用 write_auth_projection_app 再写 .app.json。

调用关系:多个认证投影测试都会先调用它造测试插件;它把写 App 的小活交给 write_auth_projection_app。

调用图:调用 1 个内部函数(write_auth_projection_app);被 5 处调用(plugin_auth_projection_hides_apps_without_chatgpt_auth, plugin_auth_projection_hides_dual_surface_mcp_with_agent_identity_apps_route, plugin_auth_projection_hides_matching_mcp_with_chatgpt_apps_route, plugin_auth_projection_keeps_non_conflicting_mcp_with_chatgpt_apps_route, plugin_auth_projection_reprojects_cached_plugins_when_auth_changes);外部调用 3 个(join, write_file, format!)。

write_auth_projection_app107–117 ↗
fn write_auth_projection_app(codex_home: &Path, plugin_name: &str, app_name: &str)

作用:给某个测试插件写一份 .app.json,声明一个 App 连接器。App 连接器可以理解成 ChatGPT 后端里的某个外部应用入口。

数据流:输入 Codex 家目录、插件名和 App 名 → 找到插件缓存目录 → 写入包含 connector_id 的 .app.json 文件。

调用关系:它被 write_auth_projection_plugin 和少数测试直接调用,用来制造 MCP 名和 App 名是否冲突的场景。

调用图:被 2 处调用(plugin_auth_projection_keeps_non_conflicting_mcp_with_chatgpt_apps_route, write_auth_projection_plugin);外部调用 3 个(join, write_file, format!)。

app_declaration119–125 ↗
fn app_declaration(name: &str, connector_id: &str) -> AppDeclaration

作用:快速造一个 AppDeclaration 测试对象,避免每个测试都手写重复字段。

数据流:输入 App 名和连接器 ID → 填成 AppDeclaration 结构 → 返回给断言使用。

调用关系:加载插件、列市场、读取插件详情等测试会用它组装期望值。

auth_projection_config127–140 ↗
async fn auth_projection_config(codex_home: &Path) -> PluginsConfigInput

作用:写一份启用 sample 和 docs 两个插件的测试配置,并把它读成插件管理器能用的配置对象。

数据流:输入 Codex 家目录 → 写 config.toml,打开 plugins 功能并启用两个插件 → 调用 load_config 读取配置 → 返回 PluginsConfigInput。

调用关系:认证投影相关测试都会用它准备统一配置;它把实际读配置的事交给 load_config。

调用图:调用 1 个内部函数(load_config);被 5 处调用(plugin_auth_projection_hides_apps_without_chatgpt_auth, plugin_auth_projection_hides_dual_surface_mcp_with_agent_identity_apps_route, plugin_auth_projection_hides_matching_mcp_with_chatgpt_apps_route, plugin_auth_projection_keeps_non_conflicting_mcp_with_chatgpt_apps_route, plugin_auth_projection_reprojects_cached_plugins_when_auth_changes);外部调用 2 个(join, write_file)。

sorted_effective_mcp_server_names142–150 ↗
fn sorted_effective_mcp_server_names(outcome: &PluginLoadOutcome) -> Vec<String>

作用:把加载结果里的有效 MCP 服务器名字取出来并排序,方便测试稳定比较。

数据流:输入 PluginLoadOutcome → 读取 effective_mcp_servers 的键 → 转成列表并排序 → 返回名字列表。

调用关系:认证投影测试用它避免 HashMap 顺序不稳定导致测试偶尔失败。

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

plugin_auth_projection_hides_apps_without_chatgpt_auth153–178 ↗
async fn plugin_auth_projection_hides_apps_without_chatgpt_auth()

作用:验证不是 ChatGPT 类登录时,插件里的 App 能力会被隐藏,但 MCP 服务器仍然可用。

数据流:先造两个插件和配置 → 用 ApiKey 认证创建管理器 → 加载插件 → 检查 App 列表为空、MCP 服务器包含 sample 和 docs、能力摘要也不暴露 App。

调用关系:使用 write_auth_projection_plugin、auth_projection_config 和 sorted_effective_mcp_server_names,覆盖认证过滤规则的一种分支。

调用图:调用 3 个内部函数(new_with_options, auth_projection_config, write_auth_projection_plugin);外部调用 3 个(new, assert!, assert_eq!)。

plugin_auth_projection_hides_matching_mcp_with_chatgpt_apps_route181–219 ↗
async fn plugin_auth_projection_hides_matching_mcp_with_chatgpt_apps_route()

作用:验证 ChatGPT 登录时,如果同一个名字同时有 App 和 MCP,系统会走 App 路线并隐藏对应 MCP。

数据流:造 sample 插件同时带 App 和 MCP,docs 只有 MCP → 用 ChatGPT 认证加载 → 输出中 sample 只显示 App,docs 仍显示 MCP。

调用关系:接在通用造插件辅助函数之后,专门测试 App 与 MCP 同名冲突时的取舍。

调用图:调用 3 个内部函数(new_with_options, auth_projection_config, write_auth_projection_plugin);外部调用 3 个(new, assert!, assert_eq!)。

plugin_auth_projection_hides_dual_surface_mcp_with_agent_identity_apps_route222–243 ↗
async fn plugin_auth_projection_hides_dual_surface_mcp_with_agent_identity_apps_route()

作用:验证 AgentIdentity 登录方式和 ChatGPT 登录类似,也会优先显示 App 并隐藏冲突的 MCP。

数据流:准备 sample 和 docs 插件 → 用 AgentIdentity 认证加载 → 得到 sample 的 App,MCP 只剩 docs。

调用关系:复用认证投影测试夹具,确认另一个认证模式走同样的投影规则。

调用图:调用 3 个内部函数(new_with_options, auth_projection_config, write_auth_projection_plugin);外部调用 2 个(new, assert_eq!)。

plugin_auth_projection_keeps_non_conflicting_mcp_with_chatgpt_apps_route246–278 ↗
async fn plugin_auth_projection_keeps_non_conflicting_mcp_with_chatgpt_apps_route()

作用:验证 ChatGPT 登录时,只有和 App 同名冲突的 MCP 会被隐藏;不冲突的 MCP 仍保留。

数据流:给 sample 写 MCP,再单独写一个不同名 App → 加载后检查 sample 的 MCP 和 App 都存在,docs 的 MCP 也存在。

调用关系:直接调用 write_auth_projection_app 制造“名字不冲突”的场景,补足上一条冲突测试。

调用图:调用 4 个内部函数(new_with_options, auth_projection_config, write_auth_projection_app, write_auth_projection_plugin);外部调用 2 个(new, assert_eq!)。

plugin_auth_projection_preserves_duplicate_connector_declaration_names281–361 ↗
async fn plugin_auth_projection_preserves_duplicate_connector_declaration_names()

作用:验证同一个插件里多个 App 声明指向同一个连接器时,最终只显示一次连接器,同时隐藏对应的冲突 MCP。

数据流:写一个插件,里面有 foo、foo2、other 三个 MCP,foo 和 foo2 的 App 都指向同一 connector → ChatGPT 加载 → 结果只保留一个 connector_shared,MCP 只剩 other。

调用关系:这是认证投影规则的边界测试,不走辅助造插件函数,而是手写更复杂的配置文件。

调用图:调用 2 个内部函数(new_with_options, load_config);外部调用 3 个(new, assert_eq!, write_file)。

plugin_auth_projection_reprojects_cached_plugins_when_auth_changes364–435 ↗
async fn plugin_auth_projection_reprojects_cached_plugins_when_auth_changes()

作用:验证同一批已缓存的插件,在登录方式改变后会重新按新认证方式筛选能力。

数据流:先用 ChatGPT 加载,得到 App 路线结果 → 把管理器认证改成 ApiKey → 再加载同一配置 → 结果变成不显示 App、恢复 MCP。

调用关系:它检查缓存不会把旧认证模式下的结果错误复用,是认证投影和加载缓存之间的联动测试。

调用图:调用 3 个内部函数(new_with_options, auth_projection_config, write_auth_projection_plugin);外部调用 3 个(new, assert!, assert_eq!)。

write_plugin_with_version437–456 ↗
fn write_plugin_with_version(
    root: &Path,
    dir_name: &str,
    manifest_name: &str,
    manifest_version: Option<&str>,
)

作用:在指定目录写一个带可选版本号的测试插件。版本号用于测试安装和缓存刷新时该放到哪个版本目录。

数据流:输入根目录、目录名、清单里的名字和可选版本 → 创建 .codex-plugin 与 skills 目录 → 写 plugin.json、技能文件和空 .mcp.json。

调用关系:许多安装、刷新缓存测试都靠它快速准备插件;write_plugin 是它的简化包装。

调用图:被 8 处调用(install_plugin_uses_manifest_version_for_non_curated_plugins, refresh_non_curated_plugin_cache_ignores_invalid_unconfigured_plugin_versions, refresh_non_curated_plugin_cache_refreshes_configured_git_source, refresh_non_curated_plugin_cache_reinstalls_missing_configured_plugin_with_manifest_version, refresh_non_curated_plugin_cache_replaces_existing_local_version_with_manifest_version, refresh_non_curated_plugin_cache_returns_false_when_configured_plugins_are_current, write_cached_plugin, write_plugin);外部调用 4 个(join, format!, create_dir_all, write)。

write_plugin458–465 ↗
fn write_plugin(root: &Path, dir_name: &str, manifest_name: &str)

作用:写一个没有 manifest 版本号的普通测试插件。适合测试本地插件默认使用 local 版本的场景。

数据流:输入根目录、插件目录名和插件名 → 调用 write_plugin_with_version,并把版本参数留空 → 得到一个最小可加载插件。

调用关系:被安装、卸载、市场列表、缓存刷新和 hook 测试大量复用。

调用图:调用 1 个内部函数(write_plugin_with_version);被 13 处调用(install_plugin_supports_git_subdir_marketplace_sources, install_plugin_supports_relative_git_subdir_marketplace_sources, install_plugin_updates_config_with_relative_path_and_plugin_key, list_marketplaces_includes_enabled_state, plugin_cache_ignores_unrelated_session_overrides, plugin_hooks_for_layer_stack_loads_configured_plugin_hooks, refresh_curated_plugin_cache_migrates_full_sha_cache_version_to_short_version, refresh_curated_plugin_cache_removes_cache_for_plugin_removed_from_marketplace, refresh_curated_plugin_cache_replaces_existing_local_version_with_short_sha_version, refresh_curated_plugin_cache_returns_false_when_configured_plugins_are_current (+3 more))。

init_git_repo467–473 ↗
fn init_git_repo(repo: &Path)

作用:把一个测试目录初始化成 Git 仓库,并提交当前文件。这样测试 Git 来源插件时有真实仓库可用。

数据流:输入仓库路径 → 依次运行 git init、设置用户名邮箱、add、commit → 仓库变成可克隆的测试源。

调用关系:它把具体命令执行交给 run_git,被 Git 子目录插件安装和刷新测试调用。

调用图:调用 1 个内部函数(run_git);被 3 处调用(install_plugin_supports_git_subdir_marketplace_sources, install_plugin_supports_relative_git_subdir_marketplace_sources, refresh_non_curated_plugin_cache_refreshes_configured_git_source)。

run_git475–490 ↗
fn run_git(repo: &Path, args: &[&str])

作用:在指定目录运行一条 git 命令,如果失败就把 stdout 和 stderr 打出来,方便定位测试问题。

数据流:输入仓库路径和 git 参数 → 启动 git -C 命令 → 成功则无返回,失败则让测试失败并显示错误信息。

调用关系:只被 init_git_repo 调用,是本测试文件里操作 Git 的底层小工具。

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

plugin_config_toml492–510 ↗
fn plugin_config_toml(enabled: bool, plugins_feature_enabled: bool) -> String

作用:生成一份最常见的插件测试配置文本,可以控制插件是否启用、插件总开关是否启用。

数据流:输入插件 enabled 值和 features.plugins 值 → 拼出 TOML 表结构 → 序列化成字符串返回。

调用关系:大量加载测试用它减少重复配置文本,后续通常交给 load_plugins_from_config。

调用图:被 12 处调用(capability_summary_sanitizes_plugin_descriptions_to_one_line, capability_summary_truncates_overlong_plugin_descriptions, effective_apps_preserves_app_config_order, load_plugins_ignores_invalid_manifest_skills_shape, load_plugins_ignores_manifest_component_paths_without_dot_slash, load_plugins_ignores_project_config_files, load_plugins_loads_default_skills_and_mcp_servers, load_plugins_preserves_disabled_plugins_without_effective_contributions, load_plugins_returns_empty_when_feature_disabled, load_plugins_uses_manifest_configured_component_paths (+2 more));外部调用 4 个(Boolean, Table, new, to_string)。

load_plugins_from_config512–522 ↗
async fn load_plugins_from_config(
    config_toml: &str,
    codex_home: &Path,
    auth_mode: Option<AuthMode>,
) -> PluginLoadOutcome

作用:把一段配置文本写入临时 Codex 家目录,然后真正调用插件管理器加载插件。

数据流:输入 config.toml 内容、Codex 家目录和认证模式 → 写配置文件 → 调 load_config 读配置 → 创建 PluginsManager → 返回 PluginLoadOutcome。

调用关系:这是多数加载类测试的统一入口,把造配置、读配置、加载插件串起来。

调用图:调用 2 个内部函数(new_with_options, load_config);被 13 处调用(capability_summary_sanitizes_plugin_descriptions_to_one_line, capability_summary_truncates_overlong_plugin_descriptions, effective_apps_dedupes_connector_ids_across_plugins, effective_apps_preserves_app_config_order, load_plugins_applies_plugin_mcp_server_policy, load_plugins_ignores_invalid_manifest_skills_shape, load_plugins_ignores_manifest_component_paths_without_dot_slash, load_plugins_ignores_unknown_disabled_skill_names, load_plugins_loads_default_skills_and_mcp_servers, load_plugins_preserves_disabled_plugins_without_effective_contributions (+3 more));外部调用 3 个(join, to_path_buf, write_file)。

load_config524–526 ↗
async fn load_config(codex_home: &Path, cwd: &Path) -> PluginsConfigInput

作用:读取测试用的插件配置,包装项目里的真实配置加载逻辑。

数据流:输入 Codex 家目录和当前工作目录 → 调用测试支持里的 load_plugins_config → 返回 PluginsConfigInput。

调用关系:几乎所有需要配置对象的测试都会调用它,是测试和真实配置加载之间的薄适配层。

调用图:被 32 处调用(auth_projection_config, featured_plugin_ids_for_config_defaults_query_param_to_codex, featured_plugin_ids_for_config_uses_restriction_product_query_param, list_marketplaces_can_skip_openai_curated_before_loading, list_marketplaces_excludes_plugins_with_explicit_empty_products, list_marketplaces_ignores_installed_roots_missing_from_config, list_marketplaces_includes_curated_repo_marketplace, list_marketplaces_includes_enabled_state, list_marketplaces_includes_installed_marketplace_roots, list_marketplaces_installed_git_source_reads_metadata_from_cache_without_cloning (+15 more));外部调用 1 个(load_plugins_config)。

remote_installed_linear_plugin528–530 ↗
fn remote_installed_linear_plugin() -> RemoteInstalledPlugin

作用:快速造一个名叫 linear 的远程已安装插件对象。

数据流:无输入 → 调用 remote_installed_plugin 传入 linear → 返回 RemoteInstalledPlugin。

调用关系:远程缓存元数据测试用它作为默认样本。

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

remote_installed_plugin532–534 ↗
fn remote_installed_plugin(name: &str) -> RemoteInstalledPlugin

作用:快速造一个指定名字、属于全局远程市场的已安装插件对象。

数据流:输入插件名 → 调用 remote_installed_plugin_in_marketplace 并使用全局市场名 → 返回远程插件记录。

调用关系:它是远程插件测试的便利包装,底层字段由 remote_installed_plugin_in_marketplace 填。

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

remote_installed_plugin_in_marketplace536–551 ↗
fn remote_installed_plugin_in_marketplace(
    name: &str,
    marketplace_name: &str,
) -> RemoteInstalledPlugin

作用:造一个远程已安装插件对象,并允许指定它属于哪个远程市场。

数据流:输入插件名和市场名 → 填入远程 ID、启用状态、安装策略、认证策略等默认字段 → 返回 RemoteInstalledPlugin。

调用关系:被远程市场过滤和冲突处理测试使用,用来模拟后端缓存数据。

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

write_cached_plugin553–563 ↗
fn write_cached_plugin(codex_home: &Path, marketplace_name: &str, plugin_name: &str)

作用:在插件缓存目录里写一个指定市场和名字的已缓存插件。

数据流:输入 Codex 家目录、市场名和插件名 → 组合 plugins/cache 路径 → 调 write_plugin_with_version 写 local 版本插件。

调用关系:远程/本地 curated 冲突测试和缓存刷新测试用它准备已有缓存。

调用图:调用 1 个内部函数(write_plugin_with_version);被 3 处调用(refresh_curated_plugin_cache_leaves_api_curated_plugin_when_api_manifest_missing, remote_installed_cache_prefers_local_curated_conflicts_when_remote_plugin_disabled, remote_installed_cache_prefers_remote_curated_conflicts_when_remote_plugin_enabled);外部调用 1 个(join)。

load_plugins_loads_default_skills_and_mcp_servers566–683 ↗
async fn load_plugins_loads_default_skills_and_mcp_servers()

作用:验证启用插件后,默认位置的技能、MCP 服务器和 App 声明都会被加载出来。

数据流:写 plugin.json、skills、.mcp.json、.app.json → 用配置加载 → 检查 LoadedPlugin、能力摘要、有效技能根、MCP 和 App 都符合预期。

调用关系:这是插件加载最基础的完整路径测试,通过 load_plugins_from_config 驱动真实加载逻辑。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

load_plugins_applies_plugin_mcp_server_policy686–752 ↗
async fn load_plugins_applies_plugin_mcp_server_policy()

作用:验证用户配置可以覆盖插件自带的 MCP 服务器策略,比如禁用服务器、修改工具审批方式和工具白黑名单。

数据流:插件先声明一个 MCP 服务器及默认工具策略 → 配置里覆盖这些字段 → 加载后读取服务器配置,确认最终值来自用户配置。

调用关系:使用 load_plugins_from_config,专门检查插件声明和用户配置合并规则。

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

remote_installed_cache_ignores_plugins_missing_local_cache755–771 ↗
async fn remote_installed_cache_ignores_plugins_missing_local_cache()

作用:验证远程已安装缓存里有插件,但本地没有实际缓存文件时,加载结果不会凭空出现插件。

数据流:写配置打开远程插件 → 写远程已安装缓存 linear → 不写本地插件文件 → 加载后结果为空。

调用关系:测试 PluginsManager 读取远程缓存时仍要求本地缓存存在。

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

remote_installed_cache_prefers_local_curated_conflicts_when_remote_plugin_disabled774–814 ↗
async fn remote_installed_cache_prefers_local_curated_conflicts_when_remote_plugin_disabled()

作用:验证关闭 remote_plugin 功能时,本地 curated 插件和远程 curated 插件重名,优先保留本地 curated。

数据流:配置启用本地 curated 插件并关闭远程插件 → 写本地和远程缓存中重名 linear → 加载后 linear 来自 openai-curated,remote-only 仍可从远程缓存出现。

调用关系:使用 write_cached_plugin 和远程缓存写入,覆盖远程功能关闭时的冲突策略。

调用图:调用 3 个内部函数(new, load_config, write_cached_plugin);外部调用 4 个(new, assert_eq!, write_file, vec!)。

remote_installed_cache_prefers_remote_curated_conflicts_when_remote_plugin_enabled817–861 ↗
async fn remote_installed_cache_prefers_remote_curated_conflicts_when_remote_plugin_enabled()

作用:验证打开 remote_plugin 功能时,远程 curated 插件会替代同名本地 curated 插件。

数据流:配置打开远程插件并启用多个 curated 插件 → 写本地、API curated 和远程缓存 → 加载后同名 linear 选远程版本,calendar 仍保留本地版本。

调用关系:和上一条成对出现,测试远程功能开关对冲突处理的影响。

调用图:调用 3 个内部函数(new, load_config, write_cached_plugin);外部调用 4 个(new, assert_eq!, write_file, vec!)。

build_remote_installed_plugin_marketplaces_from_cache_uses_remote_metadata864–936 ↗
async fn build_remote_installed_plugin_marketplaces_from_cache_uses_remote_metadata()

作用:验证把远程已安装缓存转换成市场列表时,会保留后端给的安装策略、认证策略、关键词和展示信息。

数据流:造一个带丰富元数据的远程 linear 插件并写入缓存 → 从缓存构建市场 → 检查市场名、插件 ID、远程 ID、展示名、描述等字段。

调用关系:直接测试 PluginsManager 的远程市场构建方法,不走完整插件加载流程。

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

build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name939–967 ↗
async fn build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name()

作用:验证从远程缓存生成市场时,只返回请求的市场,不把别的远程市场混进来。

数据流:缓存里写 workspace 和 shared-with-me 两个远程插件 → 只请求 workspace 市场 → 输出只包含 workspace 插件。

调用关系:补充上一条元数据测试,重点检查市场名过滤。

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

load_plugins_resolves_disabled_skill_names_against_loaded_plugin_skills970–1009 ↗
async fn load_plugins_resolves_disabled_skill_names_against_loaded_plugin_skills()

作用:验证配置里禁用某个技能名时,系统能把名字解析到实际 SKILL.md 路径,并让插件不再贡献技能。

数据流:写一个带 sample-search 技能的插件 → 配置禁用 sample:sample-search → 加载后 disabled_skill_paths 包含真实路径,has_enabled_skills 变成 false,摘要为空。

调用关系:使用 load_plugins_from_config 检查技能配置和插件加载结果之间的映射。

调用图:调用 1 个内部函数(load_plugins_from_config);外部调用 5 个(new, assert!, assert_eq!, write_file, canonicalize)。

load_plugins_ignores_unknown_disabled_skill_names1012–1054 ↗
async fn load_plugins_ignores_unknown_disabled_skill_names()

作用:验证配置里禁用不存在的技能名时,不会误伤插件已有技能。

数据流:写 sample-search 技能 → 配置禁用 missing-skill → 加载后禁用路径为空,插件仍有有效技能,能力摘要仍显示有技能。

调用关系:和上一条配对,覆盖“禁用目标不存在”的安全行为。

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

plugin_telemetry_metadata_uses_default_mcp_config_path1057–1099 ↗
async fn plugin_telemetry_metadata_uses_default_mcp_config_path()

作用:验证生成插件遥测元数据时,会读取默认的 .mcp.json 路径来总结 MCP 能力。

数据流:写 plugin.json 和默认 .mcp.json → 调 plugin_telemetry_metadata_from_root → 检查能力摘要里包含 sample MCP。

调用关系:直接测试遥测元数据读取函数,确保它和普通加载使用相同默认路径。

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

capability_summary_sanitizes_plugin_descriptions_to_one_line1102–1136 ↗
async fn capability_summary_sanitizes_plugin_descriptions_to_one_line()

作用:验证能力摘要里的插件描述会被整理成一行,去掉换行、制表和多余空格。

数据流:写带换行和多空格的 description → 加载插件 → 原始 manifest_description 保持不变,但摘要 description 变成干净的一行。

调用关系:用 load_plugins_from_config 检查面向界面展示的摘要清洗规则。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

capability_summary_truncates_overlong_plugin_descriptions1139–1176 ↗
async fn capability_summary_truncates_overlong_plugin_descriptions()

作用:验证过长的插件描述在能力摘要里会被截断,避免把巨大文本塞进摘要。

数据流:写超过最大长度的 description → 加载插件 → 原始描述保留完整,摘要描述只保留允许的最大长度。

调用关系:和描述清洗测试一起保护能力摘要的展示质量和大小限制。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 4 个(new, assert_eq!, write_file, format!)。

load_plugins_uses_manifest_configured_component_paths1179–1292 ↗
async fn load_plugins_uses_manifest_configured_component_paths()

作用:验证 plugin.json 里用 ./ 开头指定自定义 skills、MCP、Apps 路径时,加载器会使用这些自定义文件。

数据流:同时写默认组件和自定义组件 → manifest 指向自定义路径 → 加载后技能根包含自定义和默认技能目录,MCP 和 App 来自自定义配置。

调用关系:测试插件清单对组件路径的影响,走 load_plugins_from_config。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

load_plugins_ignores_manifest_component_paths_without_dot_slash1295–1405 ↗
async fn load_plugins_ignores_manifest_component_paths_without_dot_slash()

作用:验证 manifest 里没有以 ./ 开头的组件路径会被忽略,系统回退到默认位置。

数据流:写默认和自定义组件,但 manifest 路径不带 ./ → 加载后只使用默认 skills、.mcp.json 和 .app.json。

调用关系:和上一条成对,确认路径安全规则只接受显式相对路径。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

load_plugins_ignores_invalid_manifest_skills_shape1408–1443 ↗
async fn load_plugins_ignores_invalid_manifest_skills_shape()

作用:验证 manifest 里的 skills 字段形状不对时,不会报错炸掉,而是使用默认技能目录。

数据流:写 skills 为数组而不是字符串 → 加载插件 → error 仍为空,skill_roots 回到默认 skills 目录。

调用关系:测试加载器对格式错误 manifest 的容错能力。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

load_plugins_preserves_disabled_plugins_without_effective_contributions1446–1498 ↗
async fn load_plugins_preserves_disabled_plugins_without_effective_contributions()

作用:验证配置中禁用的插件仍会出现在加载结果里,但不会贡献技能、MCP 或 App。

数据流:写一个带 MCP 的插件 → 配置 enabled=false → 加载后 LoadedPlugin 记录存在且 enabled=false,但能力相关字段为空,有效集合也为空。

调用关系:检查“记录插件状态”和“启用插件能力”这两件事被清楚分开。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 4 个(new, assert!, assert_eq!, write_file)。

effective_apps_dedupes_connector_ids_across_plugins1501–1574 ↗
async fn effective_apps_dedupes_connector_ids_across_plugins()

作用:验证多个插件声明同一个 App 连接器时,最终有效 App 列表会去重。

数据流:写 plugin-a 和 plugin-b,二者都声明 connector_example,plugin-b 还声明 connector_gmail → 加载后有效 App 只包含 example 一次和 gmail 一次。

调用关系:通过 load_plugins_from_config 检查跨插件 App 去重规则。

调用图:调用 1 个内部函数(load_plugins_from_config);外部调用 7 个(new, Boolean, Table, assert_eq!, write_file, new, to_string)。

effective_apps_preserves_app_config_order1577–1619 ↗
async fn effective_apps_preserves_app_config_order()

作用:验证 App 去重时保留第一次出现的顺序,不会乱排序。

数据流:一个插件依次声明 slack、github、slack-copy,其中 slack-copy 连接器重复 → 加载后输出 slack 再 github。

调用关系:补充 App 去重测试,关注输出顺序的稳定性。

调用图:调用 2 个内部函数(load_plugins_from_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

capability_index_filters_inactive_and_zero_capability_plugins1622–1724 ↗
fn capability_index_filters_inactive_and_zero_capability_plugins()

作用:验证能力摘要只展示真正启用且有能力的插件,隐藏空插件、禁用插件和加载失败插件。

数据流:手工构造多个 LoadedPlugin:有技能、有 MCP/App、空、禁用、失败 → 调 PluginLoadOutcome::from_plugins → 检查摘要只包含有效能力插件。

调用关系:不读文件,直接测试能力摘要索引的过滤规则。

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

load_plugins_returns_empty_when_feature_disabled1727–1755 ↗
async fn load_plugins_returns_empty_when_feature_disabled()

作用:验证总开关 features.plugins 关闭时,即使插件配置和文件都存在,也不会加载任何插件。

数据流:写插件文件和配置,但 plugins 功能为 false → 读取配置并加载 → 得到默认空结果。

调用关系:检查插件系统的总开关优先级。

调用图:调用 3 个内部函数(new, load_config, plugin_config_toml);外部调用 3 个(new, assert_eq!, write_file)。

plugin_cache_ignores_unrelated_session_overrides1758–1825 ↗
async fn plugin_cache_ignores_unrelated_session_overrides()

作用:验证插件加载缓存不会因为无关的会话配置变化而失效。

数据流:第一次用 session model=first 加载并缓存插件 → 删除 .mcp.json → 第二次只改 session model=second → 仍返回第一次缓存结果,说明无关配置没有触发重载。

调用关系:测试插件缓存键只包含会影响插件的配置,而不是所有会话参数。

调用图:调用 3 个内部函数(new, plugin_config_toml, write_plugin);外部调用 5 个(new, assert_eq!, write_file, remove_file, from_str)。

loaded_plugins_cache_invalidation_rejects_stale_load_completion1828–1842 ↗
fn loaded_plugins_cache_invalidation_rejects_stale_load_completion()

作用:验证清空缓存后,旧一轮异步加载完成时不能把过期结果写回缓存。

数据流:记录旧 generation → 清空缓存让 generation 更新 → 用旧 generation 尝试写缓存 → 再读取缓存应为空。

调用关系:直接测试缓存代数机制,防止竞态条件,也就是两个异步任务先后完成导致旧结果覆盖新状态。

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

load_plugins_rejects_invalid_plugin_keys1845–1883 ↗
async fn load_plugins_rejects_invalid_plugin_keys()

作用:验证配置里的插件键必须是 plugin@marketplace 格式,否则加载结果会带错误且不生效。

数据流:写插件文件,但配置键只写 sample → 加载后有一个插件记录,error 提示格式错误,有效技能和 MCP 都为空。

调用关系:检查配置解析的输入校验,避免模糊插件来源。

调用图:调用 1 个内部函数(load_plugins_from_config);外部调用 8 个(new, Boolean, Table, assert!, assert_eq!, write_file, new, to_string)。

install_plugin_updates_config_with_relative_path_and_plugin_key1886–1937 ↗
async fn install_plugin_updates_config_with_relative_path_and_plugin_key()

作用:验证从本地市场安装插件后,会复制到缓存,并把 plugin@marketplace 写入 config.toml。

数据流:造一个市场和本地插件 → 调 install_plugin → 检查返回的安装路径、插件 ID、认证策略,并读取 config.toml 确认插件被启用。

调用关系:测试安装流程的文件复制和配置写入两部分。

调用图:调用 3 个内部函数(new, write_plugin, try_from);外部调用 6 个(assert!, assert_eq!, create_dir_all, read_to_string, write, tempdir)。

install_openai_curated_plugin_uses_short_sha_cache_version1940–1973 ↗
async fn install_openai_curated_plugin_uses_short_sha_cache_version()

作用:验证安装 OpenAI curated 插件时,缓存版本目录使用短 SHA,而不是 local。

数据流:写 curated 市场和当前 SHA → 安装 slack → 检查安装结果里的版本和路径是测试用短 SHA。

调用关系:覆盖官方 curated 插件的特殊版本规则。

调用图:调用 3 个内部函数(new, curated_plugins_repo_path, try_from);外部调用 5 个(assert_eq!, write_curated_plugin_sha_with, write_openai_curated_marketplace, format!, tempdir)。

install_plugin_uses_manifest_version_for_non_curated_plugins1976–2027 ↗
async fn install_plugin_uses_manifest_version_for_non_curated_plugins()

作用:验证非 curated 插件安装时,会使用插件 manifest 里的 version 作为缓存目录名。

数据流:写一个 version=1.2.3-beta+7 的插件和市场 → 安装 → 返回结果和缓存路径都使用这个版本号。

调用关系:和 curated 安装测试相对,检查普通市场插件的版本来源。

调用图:调用 3 个内部函数(new, write_plugin_with_version, try_from);外部调用 4 个(assert_eq!, create_dir_all, write, tempdir)。

install_plugin_supports_git_subdir_marketplace_sources2030–2083 ↗
async fn install_plugin_supports_git_subdir_marketplace_sources()

作用:验证市场里的插件来源可以是另一个 Git 仓库的子目录。

数据流:创建远程 Git 仓库,插件放在 plugins/toolkit → 市场声明 git-subdir 来源 → 安装后缓存里出现 toolkit 的 plugin.json。

调用关系:使用 init_git_repo 准备真实 Git 来源,测试安装器拉取子目录的能力。

调用图:调用 4 个内部函数(new, init_git_repo, write_plugin, try_from);外部调用 7 个(assert!, assert_eq!, format!, create_dir_all, write, tempdir, from_directory_path)。

install_plugin_supports_relative_git_subdir_marketplace_sources2086–2134 ↗
async fn install_plugin_supports_relative_git_subdir_marketplace_sources()

作用:验证 git-subdir 来源的 URL 可以是相对于市场仓库的路径。

数据流:在市场仓库旁边创建相对路径 Git 仓库 → 市场用 ./remote-plugin-repo 声明来源 → 安装后插件进入缓存。

调用关系:补充上一条,测试相对 Git URL 的解析。

调用图:调用 4 个内部函数(new, init_git_repo, write_plugin, try_from);外部调用 5 个(assert!, assert_eq!, create_dir_all, write, tempdir)。

uninstall_plugin_removes_cache_and_config_entry2137–2171 ↗
async fn uninstall_plugin_removes_cache_and_config_entry()

作用:验证卸载插件会删除缓存目录,并从配置文件移除对应插件项;重复卸载也不会出错。

数据流:先写缓存插件和启用配置 → 调 uninstall_plugin 两次 → 检查缓存目录不存在,config.toml 不再包含该插件段。

调用关系:测试卸载流程的幂等性,也就是重复执行结果仍安全。

调用图:调用 2 个内部函数(new, write_plugin);外部调用 4 个(assert!, write_file, read_to_string, tempdir)。

list_marketplaces_includes_enabled_state2174–2297 ↗
async fn list_marketplaces_includes_enabled_state()

作用:验证列出市场时,每个插件会标明是否已安装、是否在配置中启用。

数据流:写市场、两个已缓存插件和一个启用一个禁用的配置 → 调 list_marketplaces_for_config → 检查列表里的 installed/enabled 字段准确。

调用关系:测试市场展示信息和用户配置之间的合并。

调用图:调用 4 个内部函数(new, load_config, write_plugin, try_from);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_returns_empty_when_feature_disabled2300–2342 ↗
async fn list_marketplaces_returns_empty_when_feature_disabled()

作用:验证插件总开关关闭时,市场列表也直接为空。

数据流:写一个市场和插件配置,但 features.plugins=false → 列市场 → 返回空列表。

调用关系:和加载开关测试一致,确认 UI 列市场也尊重总开关。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_excludes_plugins_with_explicit_empty_products2345–2424 ↗
async fn list_marketplaces_excludes_plugins_with_explicit_empty_products()

作用:验证市场里明确写 products=[] 的插件会被排除,默认没有 products 的插件仍显示。

数据流:市场里放 disabled-plugin 和 default-plugin,前者 policy.products 为空数组 → 列市场 → 只看到 default-plugin。

调用关系:测试产品限制过滤,避免把不适用当前产品的插件显示出来。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

read_plugin_for_config_returns_plugins_disabled_when_feature_disabled2427–2473 ↗
async fn read_plugin_for_config_returns_plugins_disabled_when_feature_disabled()

作用:验证插件总开关关闭时,读取某个插件详情会返回 PluginsDisabled 错误。

数据流:写市场和配置,但 features.plugins=false → 调 read_plugin_for_config → 得到 MarketplaceError::PluginsDisabled。

调用关系:检查详情读取接口也遵守总开关。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 5 个(assert!, write_file, create_dir_all, write, tempdir)。

read_plugin_for_config_filters_mcp_servers_for_codex_backend_auth2476–2554 ↗
async fn read_plugin_for_config_filters_mcp_servers_for_codex_backend_auth()

作用:验证读取插件详情时,也会按认证方式过滤 App 和同名 MCP。

数据流:插件同时声明 sample-mcp App 和 sample-mcp MCP,另有 other-mcp → ChatGPT 读取时 App 显示、sample-mcp 隐藏;ApiKey 读取时 App 隐藏、两个 MCP 都显示。

调用关系:把认证投影规则应用到插件详情读取接口,而不仅是加载接口。

调用图:调用 3 个内部函数(new_with_options, load_config, try_from);外部调用 5 个(assert!, assert_eq!, write_file, create_dir_all, tempdir)。

read_plugin_for_config_uses_user_layer_skill_settings_only2557–2619 ↗
async fn read_plugin_for_config_uses_user_layer_skill_settings_only()

作用:验证读取市场插件详情时,只用用户层的技能设置,不受项目目录 .codex/config.toml 影响。

数据流:用户配置启用插件,项目配置禁用某技能 → 读取插件详情 → disabled_skill_paths 为空,说明项目配置没有参与。

调用关系:测试配置层级边界,避免项目配置影响市场详情预览。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 4 个(assert!, write_file, create_dir_all, tempdir)。

read_plugin_for_config_uninstalled_git_source_requires_install_without_cloning2622–2694 ↗
async fn read_plugin_for_config_uninstalled_git_source_requires_install_without_cloning()

作用:验证未安装的 Git 来源插件,读取详情时不会去克隆远程仓库,而是提示需要先安装。

数据流:市场声明一个不存在的 Git 仓库来源 → 读取详情 → 返回安装必需的原因和说明,技能/App/MCP 为空,临时 staging 目录也没创建。

调用关系:保护详情页不做昂贵或失败的网络克隆操作。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 7 个(assert!, assert_eq!, write_file, format!, create_dir_all, tempdir, from_directory_path)。

read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning2697–2867 ↗
async fn read_plugin_for_config_installed_git_source_reads_from_cache_without_cloning()

作用:验证 Git 来源插件如果已经安装,读取详情会直接看本地缓存,不去访问远程仓库。

数据流:市场指向不存在远程,但本地缓存里有完整插件文件 → 读取详情 → 得到描述、界面信息、技能、App、hook 和 MCP,并确认没有 staging 目录。

调用关系:和上一条配对,说明未安装不克隆、已安装读缓存。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 7 个(assert!, assert_eq!, write_file, format!, create_dir_all, tempdir, from_directory_path)。

list_marketplaces_installed_git_source_reads_metadata_from_cache_without_cloning2870–2983 ↗
async fn list_marketplaces_installed_git_source_reads_metadata_from_cache_without_cloning()

作用:验证列市场时,已安装的 Git 来源插件可以从缓存读取 manifest 展示信息,而不用克隆远程。

数据流:市场指向不存在远程,缓存 manifest 里有图标、logo、截图等界面字段 → 列市场 → 插件条目包含缓存里的展示元数据和绝对资源路径。

调用关系:测试市场列表接口的缓存读取逻辑,与插件详情读取缓存测试互补。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 7 个(assert!, assert_eq!, write_file, format!, create_dir_all, tempdir, from_directory_path)。

list_marketplaces_includes_curated_repo_marketplace2986–3059 ↗
async fn list_marketplaces_includes_curated_repo_marketplace()

作用:验证本地存在 OpenAI curated 仓库时,列市场会包含这个官方市场。

数据流:写 curated 仓库的 marketplace.json 和 linear 插件 → 打开插件功能 → 列市场 → 找到 openai-curated 市场及 linear 条目。

调用关系:检查官方 curated 市场的自动发现。

调用图:调用 3 个内部函数(new, load_config, curated_plugins_repo_path);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_can_skip_openai_curated_before_loading3062–3090 ↗
async fn list_marketplaces_can_skip_openai_curated_before_loading()

作用:验证调用方要求不包含 OpenAI curated 时,即使 curated manifest 是坏 JSON,也不会读取它、不会报错。

数据流:写一个损坏的 curated marketplace.json → include_openai_curated=false 列市场 → errors 为空,结果里没有 openai-curated。

调用关系:测试可选跳过官方市场的开关,避免不必要的解析错误。

调用图:调用 3 个内部函数(new, load_config, curated_plugins_repo_path);外部调用 3 个(assert_eq!, write_file, tempdir)。

list_marketplaces_uses_api_curated_manifest_when_selected3093–3181 ↗
async fn list_marketplaces_uses_api_curated_manifest_when_selected()

作用:验证在 API Key 类认证下,市场列表会使用 openai-api-curated 的 manifest。

数据流:同时写普通 curated 和 API curated manifest → 管理器认证设为 ApiKey → 列市场 → 返回 openai-api-curated 及其中 api-plugin。

调用关系:测试认证模式对官方 curated 市场选择的影响。

调用图:调用 3 个内部函数(new, load_config, curated_plugins_repo_path);外部调用 3 个(assert_eq!, write_file, tempdir)。

list_marketplaces_skips_missing_api_curated_manifest3184–3214 ↗
async fn list_marketplaces_skips_missing_api_curated_manifest()

作用:验证需要 API curated 市场但对应 manifest 缺失时,系统安静跳过,不报错。

数据流:只写损坏的普通 curated manifest,不写 API manifest → 设置 BedrockApiKey 认证 → 列市场 → 没有错误,也没有 API curated 市场。

调用关系:补充 API curated 选择逻辑的缺文件分支。

调用图:调用 3 个内部函数(new, load_config, curated_plugins_repo_path);外部调用 3 个(assert_eq!, write_file, tempdir)。

list_marketplaces_includes_installed_marketplace_roots3217–3286 ↗
async fn list_marketplaces_includes_installed_marketplace_roots()

作用:验证配置里记录过的已安装市场根目录,也会被列入市场列表。

数据流:在 installed marketplaces 根目录写 debug 市场和 sample 插件,并在 config.toml 写 marketplaces.debug → 列市场 → 找到这个安装市场和 sample 插件。

调用关系:测试除了项目里的市场,已安装市场也能被发现。

调用图:调用 3 个内部函数(marketplace_install_root, new, load_config);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_uses_config_when_known_registry_is_malformed3289–3350 ↗
async fn list_marketplaces_uses_config_when_known_registry_is_malformed()

作用:验证已知市场注册表文件损坏时,系统仍能靠 config.toml 里的市场记录发现市场。

数据流:写配置中的 debug 市场和损坏的 known_marketplaces.json → 列市场 → 仍能发现 sample@debug。

调用关系:测试注册表坏掉时的降级路径。

调用图:调用 3 个内部函数(marketplace_install_root, new, load_config);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_ignores_installed_roots_missing_from_config3353–3403 ↗
async fn list_marketplaces_ignores_installed_roots_missing_from_config()

作用:验证安装目录里有市场文件,但配置没记录该市场时,不会自动列出。

数据流:写 installed marketplace 文件,但 config.toml 没有 marketplaces.debug → 列市场 → 结果不包含这个市场路径。

调用关系:和前两条一起确定已安装市场必须受配置记录约束。

调用图:调用 3 个内部函数(marketplace_install_root, new, load_config);外部调用 5 个(assert!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_uses_first_duplicate_plugin_entry3406–3549 ↗
async fn list_marketplaces_uses_first_duplicate_plugin_entry()

作用:验证多个市场文件里出现同名同市场插件时,只保留第一次看到的条目。

数据流:repo-a 和 repo-b 都有 debug 市场且都声明 dup-plugin,repo-b 另有 b-only-plugin → 按 repo-a、repo-b 顺序列市场 → dup-plugin 只来自 repo-a,repo-b 只剩 b-only-plugin。

调用关系:测试去重和搜索顺序,避免同一个 plugin@marketplace 在界面里出现两次。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

list_marketplaces_marks_configured_plugin_uninstalled_when_cache_is_missing3552–3633 ↗
async fn list_marketplaces_marks_configured_plugin_uninstalled_when_cache_is_missing()

作用:验证配置里启用了插件,但本地缓存缺失时,市场列表会显示 enabled=true、installed=false。

数据流:写市场和启用配置,但不写缓存插件 → 列市场 → 插件条目标记未安装但已启用。

调用关系:测试配置状态和安装状态分开表达。

调用图:调用 3 个内部函数(new, load_config, try_from);外部调用 5 个(assert_eq!, write_file, create_dir_all, write, tempdir)。

refresh_curated_plugin_cache_replaces_existing_local_version_with_short_sha_version3939–3972 ↗
fn refresh_curated_plugin_cache_replaces_existing_local_version_with_short_sha_version()

作用:验证刷新官方 curated 插件缓存时,会把旧 local 版本替换成当前短 SHA 版本。

数据流:写 curated 市场、当前 SHA 和旧 local 缓存 → 调 refresh_curated_plugin_cache → local 目录消失,短 SHA 目录出现。

调用关系:测试官方插件缓存版本迁移的常见路径。

调用图:调用 3 个内部函数(write_plugin, curated_plugins_repo_path, new);外部调用 4 个(assert!, write_curated_plugin_sha_with, write_openai_curated_marketplace, tempdir)。

refresh_curated_plugin_cache_reinstalls_missing_configured_plugin_with_current_short_version3975–3998 ↗
fn refresh_curated_plugin_cache_reinstalls_missing_configured_plugin_with_current_short_version()

作用:验证配置中的官方 curated 插件如果缓存缺失,刷新时会重新安装当前版本。

数据流:写 curated 市场和 SHA,但不写插件缓存 → 刷新 → 当前短 SHA 缓存目录被创建。

调用关系:覆盖官方缓存丢失后的自修复。

调用图:调用 2 个内部函数(curated_plugins_repo_path, new);外部调用 4 个(assert!, write_curated_plugin_sha_with, write_openai_curated_marketplace, tempdir)。

refresh_curated_plugin_cache_reinstalls_missing_api_curated_plugin4001–4025 ↗
fn refresh_curated_plugin_cache_reinstalls_missing_api_curated_plugin()

作用:验证 API curated 市场里的已配置插件缓存缺失时,也能被刷新重新安装。

数据流:写普通 curated 为空、API curated 含 api-only → 刷新指定 api-only 插件 → openai-api-curated 下出现短 SHA 版本目录。

调用关系:测试 API curated 和普通 curated 使用同一刷新机制。

调用图:调用 3 个内部函数(curated_plugins_repo_path, write_openai_api_curated_marketplace, new);外部调用 4 个(assert!, write_curated_plugin_sha_with, write_openai_curated_marketplace, tempdir)。

refresh_curated_plugin_cache_leaves_api_curated_plugin_when_api_manifest_missing4028–4048 ↗
fn refresh_curated_plugin_cache_leaves_api_curated_plugin_when_api_manifest_missing()

作用:验证 API curated manifest 缺失时,刷新不会删除已有 API curated 插件缓存。

数据流:只写普通 curated manifest,已有 api-only 缓存 → 刷新 api-only → 返回 false,原 local 缓存仍在。

调用关系:保护已有缓存,避免因为 manifest 缺失误删插件。

调用图:调用 3 个内部函数(write_cached_plugin, curated_plugins_repo_path, new);外部调用 3 个(assert!, write_openai_curated_marketplace, tempdir)。

refresh_curated_plugin_cache_removes_cache_for_plugin_removed_from_marketplace4051–4075 ↗
fn refresh_curated_plugin_cache_removes_cache_for_plugin_removed_from_marketplace()

作用:验证官方市场已经移除的插件,在刷新时会删除对应缓存。

数据流:curated 市场不再包含 google-sheets,但缓存里有它 → 刷新 → google-sheets 缓存根目录被删除。

调用关系:测试官方市场下架插件后的缓存清理。

调用图:调用 3 个内部函数(write_plugin, curated_plugins_repo_path, new);外部调用 4 个(assert!, write_openai_curated_marketplace, format!, tempdir)。

curated_plugin_ids_from_config_keys_reads_latest_codex_home_user_config4078–4118 ↗
fn curated_plugin_ids_from_config_keys_reads_latest_codex_home_user_config()

作用:验证从用户配置里提取官方 curated 插件 ID 时,会读取当前最新的 config.toml。

数据流:先写包含 openai-curated、openai-api-curated 和普通 debug 插件的配置 → 只提取两个官方插件 → 再重写为空配置 → 提取结果为空。

调用关系:测试刷新 curated 缓存前用于找配置插件的辅助逻辑。

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

refresh_curated_plugin_cache_returns_false_when_configured_plugins_are_current4121–4140 ↗
fn refresh_curated_plugin_cache_returns_false_when_configured_plugins_are_current()

作用:验证官方 curated 插件缓存已经是当前短 SHA 版本时,刷新会判断无事可做。

数据流:写 curated 市场和当前版本缓存 → 调刷新 → 返回 false,表示没有改动。

调用关系:测试缓存刷新避免无意义重装。

调用图:调用 3 个内部函数(write_plugin, curated_plugins_repo_path, new);外部调用 4 个(assert!, write_openai_curated_marketplace, format!, tempdir)。

refresh_curated_plugin_cache_migrates_full_sha_cache_version_to_short_version4143–4176 ↗
fn refresh_curated_plugin_cache_migrates_full_sha_cache_version_to_short_version()

作用:验证旧缓存目录如果用完整 SHA 命名,刷新会迁移到短 SHA 命名。

数据流:写完整 SHA 版本目录 → 调刷新 → 完整 SHA 目录删除,短 SHA 目录出现。

调用关系:测试历史版本命名兼容。

调用图:调用 3 个内部函数(write_plugin, curated_plugins_repo_path, new);外部调用 4 个(assert!, write_openai_curated_marketplace, format!, tempdir)。

refresh_non_curated_plugin_cache_replaces_existing_local_version_with_manifest_version4179–4233 ↗
fn refresh_non_curated_plugin_cache_replaces_existing_local_version_with_manifest_version()

作用:验证非官方市场插件如果 manifest 有版本号,刷新会把旧 local 缓存替换成 manifest 版本。

数据流:市场插件版本为 1.2.3,本地缓存是 local → 刷新非 curated 缓存 → local 消失,1.2.3 目录出现。

调用关系:测试普通市场缓存刷新和版本识别。

调用图:调用 2 个内部函数(write_plugin, write_plugin_with_version);外部调用 4 个(assert!, write_file, create_dir_all, tempdir)。

refresh_non_curated_plugin_cache_reinstalls_missing_configured_plugin_with_manifest_version4236–4280 ↗
fn refresh_non_curated_plugin_cache_reinstalls_missing_configured_plugin_with_manifest_version()

作用:验证配置中的非官方插件缓存缺失时,刷新会按 manifest 版本重新安装。

数据流:写市场和版本 1.2.3 的源插件,但没有缓存 → 刷新 → 缓存里创建 1.2.3 目录。

调用关系:覆盖普通市场缓存丢失后的重建。

调用图:调用 1 个内部函数(write_plugin_with_version);外部调用 4 个(assert!, write_file, create_dir_all, tempdir)。

refresh_non_curated_plugin_cache_refreshes_configured_git_source4283–4339 ↗
fn refresh_non_curated_plugin_cache_refreshes_configured_git_source()

作用:验证非官方市场里的 Git 子目录来源插件,也能通过刷新缓存被拉取并安装。

数据流:创建 Git 仓库,子目录插件版本 1.2.3 → 市场声明 git-subdir 来源并配置启用 → 刷新 → 缓存中出现 1.2.3 版本。

调用关系:结合 init_git_repo,测试普通市场刷新支持 Git 来源。

调用图:调用 2 个内部函数(init_git_repo, write_plugin_with_version);外部调用 6 个(assert!, write_file, format!, create_dir_all, tempdir, from_directory_path)。

refresh_non_curated_plugin_cache_returns_false_when_configured_plugins_are_current4342–4386 ↗
fn refresh_non_curated_plugin_cache_returns_false_when_configured_plugins_are_current()

作用:验证非官方插件缓存已经是 manifest 当前版本时,刷新返回 false。

数据流:源插件和缓存插件都为 1.2.3 → 调 refresh_non_curated_plugin_cache → 没有改动,返回 false。

调用关系:测试普通市场刷新避免重复安装。

调用图:调用 1 个内部函数(write_plugin_with_version);外部调用 4 个(assert!, write_file, create_dir_all, tempdir)。

refresh_non_curated_plugin_cache_force_reinstalls_current_local_version4389–4448 ↗
fn refresh_non_curated_plugin_cache_force_reinstalls_current_local_version()

作用:验证强制刷新即使版本仍是 local,也会重新复制源插件内容。

数据流:源插件技能文件写 new skill,缓存同版本 local 里是 old skill → 调 force_reinstall → 缓存文件变成 new skill。

调用关系:测试手动强制刷新场景,适合本地开发插件更新但版本不变。

调用图:调用 1 个内部函数(write_plugin);外部调用 6 个(assert!, assert_eq!, write_file, create_dir_all, write, tempdir)。

refresh_non_curated_plugin_cache_ignores_invalid_unconfigured_plugin_versions4451–4503 ↗
fn refresh_non_curated_plugin_cache_ignores_invalid_unconfigured_plugin_versions()

作用:验证市场里未配置的插件即使版本号非法,也不会影响已配置插件的刷新。

数据流:市场有 sample-plugin 版本 1.2.3 和 broken-plugin 空白版本;配置只启用 sample-plugin → 刷新 → sample-plugin 正常安装。

调用关系:测试刷新过程只关心配置中的插件,避免无关坏插件拖垮整体。

调用图:调用 1 个内部函数(write_plugin_with_version);外部调用 4 个(assert!, write_file, create_dir_all, tempdir)。

load_plugins_ignores_project_config_files4506–4548 ↗
async fn load_plugins_ignores_project_config_files()

作用:验证加载插件时会忽略项目目录里的插件配置,只信任允许的配置层。

数据流:项目配置里启用插件,但构造的配置层来源是 Project → 调 load_plugins_from_layer_stack → 返回空列表。

调用关系:测试配置层来源过滤,避免项目仓库偷偷启用插件。

调用图:调用 4 个内部函数(new, load_plugins_from_layer_stack, plugin_config_toml, new);外部调用 7 个(new, default, assert_eq!, default, write_file, new, vec!)。

plugin_hooks_for_layer_stack_loads_configured_plugin_hooks4551–4595 ↗
async fn plugin_hooks_for_layer_stack_loads_configured_plugin_hooks()

作用:验证已配置插件里的 hooks/hooks.json 能被加载成插件 hook 来源。

数据流:写插件和 hooks.json,配置启用插件 → 调 plugin_hooks_for_layer_stack → 输出一个 hook source,且没有加载警告。

调用关系:测试插件 hook 加载入口,确保插件不仅能提供技能和 MCP,也能提供事件 hook。

调用图:调用 4 个内部函数(new, load_config, plugin_config_toml, write_plugin);外部调用 3 个(new, assert_eq!, write_file)。

core-plugins/src/discoverable_tests.rs源码 ↗
testtest

这份文件像一套“插件推荐规则的验收清单”。测试会先造一个临时的 Codex 主目录,在里面写入假的插件市场、假的插件配置、假的已安装插件,有时还会启动一个假的网络服务器来模拟远程插件服务。然后它调用真正的 PluginsManager,让系统列出“可以推荐给用户安装或启用的插件”,最后检查结果是不是符合预期。它覆盖了很多容易出错的边界:默认推荐哪些 OpenAI 精选插件;用 API key 登录时换用哪套精选市场;已经安装的插件不能再出现;配置里重复的插件要去重;缺失文件的插件要忽略;描述里的多余空格要压平;本地插件不能因为某个已加载应用就被展开;远程插件则可以根据已加载应用从缓存里补出来。整体上,它保证用户看到的插件推荐既不会重复,也不会误导。

函数细节19
returns_fallback_plugins_without_installed_apps37–61 ↗
async fn returns_fallback_plugins_without_installed_apps()

作用:测试在没有任何已安装应用时,系统会返回默认的精选插件推荐。它确认“sample”这种不该推荐的插件不会混进列表。

数据流:进去的是一个临时目录和写好的 OpenAI 精选插件市场,里面有 sample、slack、openai-developers → 测试加载插件配置,创建 PluginsManager,再请求可发现插件列表 → 出来应只剩 openai-developers 和 slack 两个推荐项,并按预期顺序返回。

调用关系:它先用 curated_plugins_repo_path 找到测试市场目录,用 write_openai_curated_marketplace 准备数据,再通过 discovery_input 组装输入,交给 list_discoverable_plugins 调用真正的插件发现逻辑,最后用断言核对结果。

调用图:调用 4 个内部函数(discovery_input, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 4 个(assert_eq!, load_plugins_config, write_openai_curated_marketplace, tempdir)。

returns_api_curated_fallback_plugins_for_direct_provider_auth64–89 ↗
async fn returns_api_curated_fallback_plugins_for_direct_provider_auth()

作用:测试当用户用 API key 这种直接服务商认证方式登录时,默认推荐来自 API 专用精选市场。这样不同登录方式能看到正确来源的插件。

数据流:进去的是临时目录、API 精选市场文件,以及一个测试 API key 认证对象 → 系统加载配置后,根据认证方式列出可推荐插件 → 出来应是 openai-developers@openai-api-curated 和 slack@openai-api-curated。

调用关系:它和普通默认推荐测试很像,但额外用 from_api_key 创建认证信息,并调用 write_openai_api_curated_marketplace 写入 API 版市场,随后仍通过 discovery_input 和 list_discoverable_plugins 走同一条发现流程。

调用图:调用 6 个内部函数(discovery_input, list_discoverable_plugins, new, curated_plugins_repo_path, write_openai_api_curated_marketplace, from_api_key);外部调用 3 个(assert_eq!, load_plugins_config, tempdir)。

returns_microsoft_fallback_plugins92–121 ↗
async fn returns_microsoft_fallback_plugins()

作用:测试当用户已经安装了 Microsoft 相关插件里的一个时,系统会推荐同一组里剩下的 Microsoft 插件。这样用户装了 Teams 后,还能被提示安装 Outlook、SharePoint 等配套插件。

数据流:进去的是包含 teams、sharepoint、outlook-email、outlook-calendar 的精选市场,并且先安装 teams → 插件发现逻辑会排除已安装的 teams,再找同组可推荐项 → 出来是 outlook-calendar、outlook-email、sharepoint。

调用关系:测试用 install_marketplace_plugin 模拟已安装 teams,然后用 discovery_input 生成发现请求,list_discoverable_plugins 负责调用真实推荐逻辑,最后断言推荐的是剩余 Microsoft 插件。

调用图:调用 5 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 4 个(assert_eq!, load_plugins_config, write_openai_curated_marketplace, tempdir)。

includes_openai_curated_when_remote_enabled124–179 ↗
async fn includes_openai_curated_when_remote_enabled()

作用:测试远程插件功能打开时,本地配置的捆绑市场和 OpenAI 精选市场都能出现在推荐结果里。它防止打开远程功能后把本地精选推荐意外屏蔽掉。

数据流:进去的是一个本地精选市场 slack、一个配置出来的 bundled 市场 chrome,以及打开 plugins 和 remote_plugin 的配置文件 → 系统读取配置和市场内容,合并可推荐插件 → 出来应同时包含 chrome@openai-bundled 和 slack@openai-curated。

调用关系:它用 write_file 手写配置和 marketplace.json,用 write_curated_plugin 写插件目录,再通过 load_plugins_config、discovery_input、list_discoverable_plugins 触发真实发现流程。

调用图:调用 4 个内部函数(discovery_input, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 7 个(assert_eq!, load_plugins_config, write_curated_plugin, write_file, write_openai_curated_marketplace, format!, tempdir)。

deduplicates_configured_marketplace_plugin182–227 ↗
async fn deduplicates_configured_marketplace_plugin()

作用:测试同一个插件既在配置里出现,又在市场里能被发现时,结果里只出现一次。这样用户界面不会展示重复条目。

数据流:进去的是一个 bundled 市场里的 sample 插件,以及 configured_plugin_ids 里同样的 sample@openai-bundled → 发现逻辑识别这是同一个插件并去重 → 出来列表长度是 1,唯一条目就是这个插件。

调用关系:它自己搭好市场文件和配置文件,然后通过 discovery_input 把“已配置插件 ID”传进去,list_discoverable_plugins 负责跑发现逻辑,断言用来确认去重生效。

调用图:调用 3 个内部函数(discovery_input, list_discoverable_plugins, new);外部调用 6 个(assert_eq!, load_plugins_config, write_curated_plugin, write_file, format!, tempdir)。

ignores_missing_marketplace_plugin230–275 ↗
async fn ignores_missing_marketplace_plugin()

作用:测试市场清单里写了某个插件,但插件实际目录或内容缺失时,系统会跳过它。这样坏掉的市场条目不会变成用户能点的假推荐。

数据流:进去的是精选市场 installed、slack,以及另一个 configured 市场里只有清单、缺少实际 sample 插件内容 → 系统读取市场时发现 sample 不完整,忽略它,同时排除已安装 installed → 出来只推荐 slack@openai-curated。

调用关系:它用 write_file 制造一个缺内容的市场,用 install_marketplace_plugin 安装 installed,再把准备好的环境交给 discovery_input 和 list_discoverable_plugins 检查真实行为。

调用图:调用 5 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 6 个(assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, format!, tempdir)。

normalizes_description278–312 ↗
async fn normalizes_description()

作用:测试插件描述里的换行、多余空格会被整理成一段干净文本。这样用户看到的插件说明不会因为文件格式乱而难读。

数据流:进去的是 slack 插件的 plugin.json,描述写成带前后空格、换行和连续空格的文本 → 发现逻辑读取插件信息并清理描述 → 出来的是 description 为 “Plugin with extra spacing” 的推荐对象,同时保留技能、MCP 服务器和应用连接器等信息。

调用关系:它先写入特殊格式的 plugin.json,再安装另一个插件用于触发排除逻辑,最后通过 list_discoverable_plugins 得到完整的 ToolSuggestDiscoverablePlugin 并精确比对。

调用图:调用 5 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 5 个(assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, tempdir)。

omits_installed_curated_plugins315–331 ↗
async fn omits_installed_curated_plugins()

作用:测试已经安装过的精选插件不会再被推荐。这样用户不会看到“安装你已经有的东西”这种重复提示。

数据流:进去的是一个只包含 slack 的精选市场,并且 slack 已经被安装 → 发现逻辑读取已安装状态后过滤掉 slack → 出来是空列表。

调用关系:它用 install_marketplace_plugin 模拟安装,再用 discovery_input 和 list_discoverable_plugins 走推荐流程,最后断言没有任何推荐。

调用图:调用 5 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 4 个(assert_eq!, load_plugins_config, write_openai_curated_marketplace, tempdir)。

omits_not_available_curated_plugins334–391 ↗
async fn omits_not_available_curated_plugins()

作用:测试市场里标记为 NOT_AVAILABLE(不可安装)的插件不会被推荐。这样还没开放或被禁止安装的插件不会出现在用户面前。

数据流:进去的是一个手写市场:installed、slack、gmail,其中 gmail 的安装策略是不可用,并且 installed 已安装 → 发现逻辑排除已安装项和不可用项 → 出来只剩 slack@openai-curated。

调用关系:它用 write_file 手写带 policy 的 marketplace.json,用 write_curated_plugin 准备插件目录,用 install_marketplace_plugin 标记 installed 已安装,再交给 list_discoverable_plugins 验证过滤规则。

调用图:调用 5 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 5 个(assert_eq!, load_plugins_config, write_curated_plugin, write_file, tempdir)。

does_not_reload_marketplace_per_plugin394–454 ↗
async fn does_not_reload_marketplace_per_plugin()

作用:测试系统不会为了每个插件都重新加载一次市场。它主要防止性能浪费,也防止同一类警告日志重复爆炸。

数据流:进去的是包含 slack、gmail、openai-developers 的市场,其中 slack 已安装,另外两个插件写了过长的默认提示,会触发警告 → 发现逻辑读取市场并处理剩余两个插件 → 出来推荐 gmail 和 openai-developers,同时日志里每个问题只按预期次数出现,没有因为反复重载而变多。

调用关系:它设置 tracing 日志收集器来抓警告,再调用 list_discoverable_plugins。测试最后检查推荐结果和日志次数,用日志侧面证明市场没有被每个插件重复读取。

调用图:调用 5 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, new, curated_plugins_repo_path);外部调用 14 个(leak, new, new, from_utf8, new, assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, format! (+4 more))。

does_not_expand_local_plugins_by_installed_apps457–474 ↗
async fn does_not_expand_local_plugins_by_installed_apps()

作用:测试本地精选插件不会因为某个应用连接器存在就被额外展开推荐。也就是说,本地插件推荐不能只靠“应用匹配”自动冒出来。

数据流:进去的是 sample、slack、hubspot 市场,其中 sample 写了应用连接器信息,slack 已安装 → 发现逻辑不会因为 sample 的应用信息就推荐它,也会排除已安装 slack → 出来是空列表。

调用关系:它用 write_plugin_app 给 sample 写 .app.json,再用 install_marketplace_plugin 安装 slack,之后通过 discovery_input 和 list_discoverable_plugins 验证本地插件不会按已安装应用扩展。

调用图:调用 6 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, write_plugin_app, new, curated_plugins_repo_path);外部调用 4 个(assert_eq!, load_plugins_config, write_openai_curated_marketplace, tempdir)。

does_not_read_local_plugins_for_loaded_apps477–515 ↗
async fn does_not_read_local_plugins_for_loaded_apps()

作用:测试当系统只根据“已加载应用”判断时,不会去读取无关本地插件的 .app.json。这样可以避免无关坏文件导致警告或失败。

数据流:进去的是 hubspot、granola、sample 三个本地插件,其中 sample 的 .app.json 故意写成非法 JSON,输入里声明 hubspot 应用已加载 → 发现逻辑不扫描无关本地插件应用文件 → 出来是空推荐,并且日志里没有 sample/.app.json 的错误记录。

调用关系:它用 write_plugin_app 写两个正常应用文件,用 write_file 写一个坏文件,再设置日志收集器。list_discoverable_plugins 跑完后,测试通过日志内容确认坏文件没有被读取。

调用图:调用 5 个内部函数(discovery_input, list_discoverable_plugins, write_plugin_app, new, curated_plugins_repo_path);外部调用 13 个(leak, new, new, from_utf8, new, assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, new (+3 more))。

does_not_expand_local_sales_apps518–586 ↗
async fn does_not_expand_local_sales_apps()

作用:测试本地安装的 sales 类插件即使声明了多个应用连接器,也不会让相关本地精选插件被自动推荐。它防止本地应用关系被误当成推荐依据。

数据流:进去的是 OpenAI 精选里的 hubspot、granola、test-source,以及另一个本地 sales 市场;sales 插件已安装并声明 hubspot、granola 应用 → 发现逻辑不按这些本地应用关系扩展推荐 → 出来是空列表。

调用关系:它用 write_plugin_app 准备精选插件应用信息,又手写 sales 市场和 .app.json,安装 sales 后通过 list_discoverable_plugins 验证不会产生额外本地推荐。

调用图:调用 6 个内部函数(discovery_input, install_marketplace_plugin, list_discoverable_plugins, write_plugin_app, new, curated_plugins_repo_path);外部调用 7 个(assert_eq!, load_plugins_config, write_curated_plugin, write_file, write_openai_curated_marketplace, format!, tempdir)。

expands_cached_remote_plugins_by_loaded_apps589–706 ↗
async fn expands_cached_remote_plugins_by_loaded_apps()

作用:测试远程插件可以根据“已加载应用连接器”从缓存里被推荐出来。和本地插件不同,远程插件目录缓存里有匹配的 app_id 时,应该能补进推荐列表。

数据流:进去的是打开 remote_plugin 的配置、一个假的远程服务器返回的全局插件 remote-unlisted,以及 loaded_plugin_app_connector_ids 里的 remote-unlisted-app → 测试先抓取并缓存远程全局插件目录,再缓存远程已安装插件市场,最后运行发现逻辑 → 出来是 remote-unlisted@openai-curated-remote,带远程插件 ID、显示名、短描述、技能标记和应用连接器 ID。

调用关系:它用 wiremock 启动假服务器并设定接口响应,用 fetch_and_cache_global_remote_plugin_catalog 写入远程目录缓存,再让 PluginsManager 构建远程已安装缓存,最后通过 list_discoverable_plugins 检查远程应用匹配推荐。

调用图:调用 5 个内部函数(discovery_input, list_discoverable_plugins, new, fetch_and_cache_global_remote_plugin_catalog, create_dummy_chatgpt_auth_for_testing);外部调用 12 个(given, start, new, assert_eq!, load_plugins_config, write_file, format!, json!, tempdir, method (+2 more))。

discovery_input708–720 ↗
fn discovery_input(
    plugins: PluginsConfigInput,
    configured_plugin_ids: &[&str],
    disabled_plugin_ids: &[&str],
    loaded_plugin_app_connector_ids: &[&str],
) -> ToolSuggestPluginDiscovery

作用:这是测试里的小帮手,用来把插件配置和几组插件 ID 包成一次“发现插件”的输入。它让每个测试不用重复写同样的结构体组装代码。

数据流:进去的是 PluginsConfigInput,以及配置过的插件 ID、禁用的插件 ID、已加载应用连接器 ID 这三组字符串切片 → 它把三组字符串都转成 HashSet,也就是不重复的字符串集合 → 出来是 ToolSuggestPluginDiscoveryInput,供插件发现函数使用。

调用关系:大多数测试都会先调用它准备输入;它内部把转换工作交给 string_set,然后这个输入通常会立刻传给 list_discoverable_plugins。

调用图:调用 1 个内部函数(string_set);被 14 处调用(deduplicates_configured_marketplace_plugin, does_not_expand_local_plugins_by_installed_apps, does_not_expand_local_sales_apps, does_not_read_local_plugins_for_loaded_apps, does_not_reload_marketplace_per_plugin, expands_cached_remote_plugins_by_loaded_apps, ignores_missing_marketplace_plugin, includes_openai_curated_when_remote_enabled, normalizes_description, omits_installed_curated_plugins (+4 more))。

list_discoverable_plugins722–731 ↗
async fn list_discoverable_plugins(
    plugins_manager: &PluginsManager,
    input: ToolSuggestPluginDiscoveryInput,
    auth: Option<&CodexAuth>,
) -> Vec<ToolSuggestDiscoverablePlugin>

作用:这是测试里的包装函数,用来调用真正的插件发现接口,并把错误处理简化掉。测试只关心结果列表,不想每次都写 expect。

数据流:进去的是 PluginsManager、发现输入和可选认证信息 → 它调用 list_tool_suggest_discoverable_plugins 等待异步结果,如果失败就让测试直接失败 → 出来是 Vec<ToolSuggestDiscoverablePlugin,也就是可推荐插件列表。

调用关系:几乎所有测试的核心一步都通过它完成;它本身不决定推荐规则,只是把测试准备好的数据交给 PluginsManager 的真实实现。

调用图:被 14 处调用(deduplicates_configured_marketplace_plugin, does_not_expand_local_plugins_by_installed_apps, does_not_expand_local_sales_apps, does_not_read_local_plugins_for_loaded_apps, does_not_reload_marketplace_per_plugin, expands_cached_remote_plugins_by_loaded_apps, ignores_missing_marketplace_plugin, includes_openai_curated_when_remote_enabled, normalizes_description, omits_installed_curated_plugins (+4 more));外部调用 1 个(list_tool_suggest_discoverable_plugins)。

string_set733–735 ↗
fn string_set(values: &[&str]) -> HashSet<String>

作用:这是一个很小的转换工具,把字符串数组变成字符串集合。集合的好处是自动去重,也方便后面的发现逻辑快速判断某个 ID 是否存在。

数据流:进去的是一组 &str 字符串引用 → 它逐个复制成 String,并收集到 HashSet 里 → 出来是不重复的字符串集合。

调用关系:它只被 discovery_input 调用,专门为测试输入里的 configured_plugin_ids、disabled_plugin_ids 和 loaded_plugin_app_connector_ids 服务。

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

install_marketplace_plugin737–749 ↗
async fn install_marketplace_plugin(codex_home: &Path, marketplace_root: &Path, plugin_name: &str)

作用:这是测试里的安装插件帮手,用来把某个市场插件装进临时 Codex 主目录。它让测试能方便地模拟“用户已经安装过这个插件”的状态。

数据流:进去的是 Codex 主目录、市场根目录和插件名 → 它先写入测试用的精选插件版本号,再创建 PluginsManager,构造 PluginInstallRequest,指向 marketplace.json → 出来没有直接返回值,但临时目录里的插件安装状态被改变;如果安装失败,测试会立刻失败。

调用关系:很多测试在验证“已安装插件要被排除”前会调用它。它把实际安装动作交给 PluginsManager.install_plugin,因此测试用到的安装状态和真实系统保持一致。

调用图:调用 2 个内部函数(new, try_from);被 8 处调用(does_not_expand_local_plugins_by_installed_apps, does_not_expand_local_sales_apps, does_not_reload_marketplace_per_plugin, ignores_missing_marketplace_plugin, normalizes_description, omits_installed_curated_plugins, omits_not_available_curated_plugins, returns_microsoft_fallback_plugins);外部调用 3 个(join, to_path_buf, write_curated_plugin_sha_with)。

write_plugin_app751–765 ↗
fn write_plugin_app(root: &Path, plugin_name: &str, app_name: &str, app_id: &str)

作用:这是测试里的文件写入帮手,用来给某个插件写一个 .app.json,声明它关联哪个应用连接器。这样测试可以模拟插件和外部应用之间的对应关系。

数据流:进去的是插件市场根目录、插件名、应用名和应用 ID → 它拼出 plugins/<插件名>/.app.json 的路径,并写入一段 JSON → 出来没有返回值,但磁盘上多了一个应用声明文件。

调用关系:涉及应用连接器匹配的测试会调用它,比如检查本地插件不会按应用自动扩展、远程和本地规则不同等;真正写文件的动作交给 write_file。

调用图:被 3 处调用(does_not_expand_local_plugins_by_installed_apps, does_not_expand_local_sales_apps, does_not_read_local_plugins_for_loaded_apps);外部调用 3 个(join, write_file, format!)。

core-plugins/src/app_mcp_routing_tests.rs源码 ↗
testtest run

这是一组测试代码,不是正式跑在线上的功能。它关心一个很实际的问题:同一个名字,比如 linear,可能既作为“应用”出现,也作为 MCP 服务器出现。MCP 可以理解成一种外部工具服务器,系统通过它调用工具。如果两边名字冲突,系统必须知道该保留谁,否则用户可能点的是应用,实际却走到另一个服务器。文件先准备几个小工具函数,用来快速造假应用、假 MCP 列表,并把名字排序,避免因为顺序不同导致测试误报。真正的测试检查三件事:哪些登录方式允许走应用路由;应用路由不可用时要清空应用列表;应用路由可用且插件启用时,要保留应用、删掉同名 MCP;但插件没启用时,不要动 MCP。它像给交通规则画了几条红线,防止以后改代码时把“谁优先通行”改坏。

函数细节8
app6–12 ↗
fn app(name: &str) -> AppDeclaration

作用:这个小帮手用一个名字快速造出一个假的应用声明,方便测试里少写重复代码。有人要测试 linear、notion 这类应用时,就用它做测试样本。

数据流:进去的是一个应用名字字符串 → 它把名字存进 AppDeclaration,并用这个名字拼出一个 connector_id,也就是连接器编号 → 出来的是一个完整的假 AppDeclaration,不会改动外部数据。

调用关系:它是测试数据工厂。几个测试在准备 apps 列表时会用它先造出应用,然后再把这些应用交给路由策略函数检查。它内部只做字符串拼接和新建编号这类简单工作。

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

mcp_servers14–19 ↗
fn mcp_servers(mcp_servers: impl IntoIterator<Item = (&'static str, i32)>) -> HashMap<String, i32>

作用:这个小帮手把一组“服务器名和值”的简短写法变成 HashMap,也就是按名字查值的表。这样测试里可以很快搭出假的 MCP 服务器清单。

数据流:进去的是若干个二元组,比如 “linear” 对应 1、“docs” 对应 2 → 它遍历这些项,把名字转成字符串,并收进 HashMap → 出来的是一张 MCP 服务器表,供测试函数修改和检查。

调用关系:它被三个路由策略测试调用,用来准备测试开始前的 MCP 服务器状态。准备好之后,测试会把这张表交给 apply_app_mcp_routing_policy,看策略会不会删除冲突项或保留它们。

调用图:被 3 处调用(app_mcp_routing_clears_apps_when_apps_route_is_unavailable, app_mcp_routing_preserves_apps_and_removes_conflicting_mcp_with_apps_route, app_mcp_routing_preserves_mcp_conflicts_when_plugin_is_inactive);外部调用 1 个(into_iter)。

sorted_app_names21–25 ↗
fn sorted_app_names(apps: &[AppDeclaration]) -> Vec<String>

作用:这个函数把应用列表里的名字拿出来并排好序,方便测试比较结果。排序是为了让测试只关心“有哪些名字”,不被列表顺序干扰。

数据流:进去的是一组 AppDeclaration 应用声明 → 它逐个读取 name 字段,复制出名字列表,然后按字母顺序排序 → 出来的是排好序的应用名列表,原来的应用列表不变。

调用关系:它在断言结果时使用,帮测试把复杂的应用对象简化成名字列表。这样 assert_eq! 比较时更直观,也更不容易因为无关字段造成干扰。

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

sorted_mcp_server_names27–31 ↗
fn sorted_mcp_server_names(mcp_servers: &HashMap<String, i32>) -> Vec<String>

作用:这个函数把 MCP 服务器表里的所有服务器名取出来并排好序,方便测试确认最后还剩哪些服务器。

数据流:进去的是一个按名字存放的 MCP 服务器 HashMap → 它读取所有键,也就是服务器名,复制出来后排序 → 出来的是排好序的服务器名列表,不改变原来的 HashMap。

调用关系:它配合测试里的 assert_eq! 使用,把 HashMap 这种不保证顺序的表变成稳定的名字列表。这样测试能准确判断路由策略删掉了谁、保留了谁。

apps_route_available_tracks_auth_mode34–39 ↗
fn apps_route_available_tracks_auth_mode()

作用:这个测试确认“应用路由是否可用”完全跟登录方式规则一致。简单说,它检查哪些身份能用应用通道,哪些不能。

数据流:进去的是几种 AuthMode 登录方式,包括 ChatGPT、AgentIdentity、ApiKey,以及没有登录方式的情况 → 它分别调用 apps_route_available,并用 assert! 检查真假是否符合预期 → 出来没有业务数据,测试通过表示规则正确,失败就会报错。

调用关系:这是最基础的规则测试。后面的路由策略测试依赖这个前提:只有某些登录方式能走应用路由,所以这里先把“门是否打开”的判断钉住。它把具体判断交给 apps_route_available。

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

app_mcp_routing_clears_apps_when_apps_route_is_unavailable42–58 ↗
fn app_mcp_routing_clears_apps_when_apps_route_is_unavailable()

作用:这个测试确认:当应用路由不可用时,系统会清空应用列表,但不会乱删 MCP 服务器。这样可以避免把用户带到一个当前不能使用的应用入口。

数据流:开始时有一个 linear 应用,还有 linear 和 docs 两个 MCP 服务器 → 它用 ApiKey 这种不允许应用路由的登录方式调用 apply_app_mcp_routing_policy → 之后应用列表应为空,MCP 服务器仍然保留 linear 和 docs。

调用关系:它先用 app 和 mcp_servers 准备数据,然后把数据交给 apply_app_mcp_routing_policy。最后用 assert! 和 assert_eq! 检查策略结果,重点验证“应用路由关门时,只清应用,不清服务器”。

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

app_mcp_routing_preserves_apps_and_removes_conflicting_mcp_with_apps_route61–80 ↗
fn app_mcp_routing_preserves_apps_and_removes_conflicting_mcp_with_apps_route()

作用:这个测试确认:当应用路由可用且插件启用时,同名冲突要让应用优先,删掉同名 MCP 服务器。这样 linear 应用就不会被 linear MCP 服务器抢走入口。

数据流:开始时有 linear、notion 两个应用,也有 linear、docs、notion 三个 MCP 服务器 → 它用允许应用路由的 Chatgpt 登录方式,并设置插件启用,然后调用 apply_app_mcp_routing_policy → 之后应用仍保留 linear 和 notion,MCP 只剩不冲突的 docs。

调用关系:它测试的是核心冲突处理场景。mcp_servers 负责搭出初始服务器表,apply_app_mcp_routing_policy 执行真正的取舍,sorted_app_names 和 sorted_mcp_server_names 帮它把结果整理成可比较的名字列表。

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

app_mcp_routing_preserves_mcp_conflicts_when_plugin_is_inactive83–99 ↗
fn app_mcp_routing_preserves_mcp_conflicts_when_plugin_is_inactive()

作用:这个测试确认:即使登录方式允许应用路由,只要插件没启用,就不要删除同名 MCP 服务器。它防止系统在插件实际不工作时过早改动服务器清单。

数据流:开始时有 linear 应用,也有 linear 和 docs 两个 MCP 服务器 → 它用允许应用路由的 Chatgpt 登录方式,但把 plugin_active 设为 false,再调用 apply_app_mcp_routing_policy → 之后应用还在,MCP 里的 linear 和 docs 也都还在。

调用关系:它覆盖了一个容易被忽略的开关条件:插件是否启用。它和前一个测试形成对照,说明不是只要登录方式允许就删除冲突 MCP,还必须插件真的处于启用状态。

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

core/src/plugins/test_support.rs源码 ↗
testtest setup and config load

插件功能要测试,就需要一套看起来像真的本地环境:有配置文件,有插件目录,有插件说明,有技能文件,有 MCP 服务器配置,还有应用连接器配置。这个文件专门负责把这些测试材料写到磁盘上。可以把它想成测试里的“布景师”:正式演出前,它先把舞台搭好。write_file 是最底层的写文件助手,会先创建父目录再写内容。其他函数在它上面拼出更具体的东西,比如一个精选插件、一个 OpenAI 精选插件市场、插件版本 SHA 文件,以及打开插件功能的配置文件。最后,load_plugins_config 会用这些临时文件真正加载一份配置,方便测试检查系统在这种环境下会怎么工作。它本身不是产品运行时的插件逻辑,而是为了让测试稳定、少重复、像真实场景一样运行。

函数细节7
write_file10–13 ↗
fn write_file(path: &Path, contents: &str)

作用:这是测试里最基础的写文件助手。别人只要给它一个路径和文字内容,它就保证目录先存在,然后把文件写进去。

数据流:输入是一个文件路径和要写入的字符串。它先查看这个文件应该放在哪个父目录下,创建这些目录;然后把字符串写成文件。结果是磁盘上多了或更新了这个文件,如果目录不存在也会被一起补齐。

调用关系:它是这个测试工具箱的地基。write_curated_pluginwrite_openai_curated_marketplacewrite_curated_plugin_sha_withwrite_plugins_feature_config 都把真正落盘的活交给它,这样上层函数只需要关心要写什么测试内容。

调用图:被 4 处调用(write_curated_plugin, write_curated_plugin_sha_with, write_openai_curated_marketplace, write_plugins_feature_config);外部调用 3 个(parent, create_dir_all, write)。

write_curated_plugin15–51 ↗
fn write_curated_plugin(root: &Path, plugin_name: &str)

作用:这个函数在测试目录里造出一个假的“精选插件”。它会写出插件说明、技能文件、MCP 配置和应用连接器配置,让测试能像面对真实插件一样运行。

数据流:输入是测试根目录和插件名字。它先拼出这个插件应该所在的目录,然后依次写入 .codex-plugin/plugin.jsonskills/SKILL.md.mcp.json.app.json。结果是磁盘上出现一个结构完整的测试插件目录。

调用关系:它通常不是单独写市场文件,而是被 write_openai_curated_marketplace 调用。后者先写插件市场清单,再让它为清单里的每个插件补上真实目录和文件。

调用图:调用 1 个内部函数(write_file);被 1 处调用(write_openai_curated_marketplace);外部调用 2 个(join, format!)。

write_openai_curated_marketplace53–83 ↗
fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str])

作用:这个函数为测试创建一个假的 OpenAI 精选插件市场。它把给定的插件名字写进市场清单,并同时把每个插件的本地文件都造出来。

数据流:输入是测试根目录和一组插件名。它把这些插件名变成 marketplace.json 里的插件列表,每个插件都指向本地 ./plugins/插件名 路径;然后写出 .agents/plugins/marketplace.json;最后逐个调用 write_curated_plugin 创建对应插件目录。结果是测试目录里同时有“市场目录”和“市场里列出的插件”。

调用关系:它是搭建完整插件市场测试场景的上层入口。它自己负责市场清单,把具体插件文件的创建交给 write_curated_plugin,而所有文件写入最终还是通过 write_file 完成。

调用图:调用 2 个内部函数(write_curated_plugin, write_file);外部调用 2 个(join, format!)。

write_curated_plugin_sha85–87 ↗
fn write_curated_plugin_sha(codex_home: &Path)

作用:这个函数写入测试默认使用的精选插件 SHA。SHA 可以理解成一串版本指纹,用来表示当前安装或同步到的是哪一版插件。

数据流:输入是 Codex 的测试主目录。它不自己拼内容,而是使用内置的测试 SHA 常量,再交给 write_curated_plugin_sha_with 写到指定位置。结果是临时目录中出现一个记录插件版本指纹的文件。

调用关系:它是一个省事包装函数,供测试直接使用默认指纹。调用图里它会被 verified_plugin_install_completed_requires_installed_plugin 这样的测试使用,真正写文件的工作则交给 write_curated_plugin_sha_with

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

write_curated_plugin_sha_with89–91 ↗
fn write_curated_plugin_sha_with(codex_home: &Path, sha: &str)

作用:这个函数把指定的插件 SHA 写到测试环境里。它适合测试不同版本指纹时使用,而不是只能用默认值。

数据流:输入是 Codex 的测试主目录和一段 SHA 字符串。它把 SHA 加上换行,写入 .tmp/plugins.sha。结果是磁盘上有一个版本指纹文件,后续代码可以读取它来判断插件状态。

调用关系:它是 write_curated_plugin_sha 背后的实际执行者。上层函数负责决定用哪个 SHA,它负责把这个 SHA 通过 write_file 安全地写到正确位置。

调用图:调用 1 个内部函数(write_file);被 1 处调用(write_curated_plugin_sha);外部调用 2 个(join, format!)。

write_plugins_feature_config93–100 ↗
fn write_plugins_feature_config(codex_home: &Path)

作用:这个函数给测试写一份配置文件,明确打开插件功能开关。没有这个配置,测试可能还没跑到插件逻辑,就因为功能没启用而走了别的分支。

数据流:输入是 Codex 的测试主目录。它把一段 TOML 配置写到配置文件中,内容是 [features] plugins = true。结果是后续加载配置时,系统会认为插件功能已经开启。

调用关系:它常在需要验证插件安装或插件状态的测试开始前使用,例如被 verified_plugin_install_completed_requires_installed_plugin 调用。它只负责写配置文件,实际落盘依赖 write_file

调用图:调用 1 个内部函数(write_file);被 1 处调用(verified_plugin_install_completed_requires_installed_plugin);外部调用 1 个(join)。

load_plugins_config102–109 ↗
async fn load_plugins_config(codex_home: &Path) -> crate::config::Config

作用:这个异步函数从测试目录加载一份 Codex 配置。异步的意思是它可能等待文件读取等操作完成,不一定马上返回。

数据流:输入是 Codex 的测试主目录。它创建一个默认的配置构建器,把 codex_home 和备用当前目录都设成这个测试目录,然后执行构建并等待完成。结果是一份已经加载好的 Config 配置对象;如果加载失败,测试会直接报错。

调用关系:它通常在前面那些写文件函数布置好测试环境之后使用。前面的函数负责把配置和插件材料放到磁盘上,它负责让真实配置加载流程读这些材料,从而让测试检查系统后续行为是否正确。

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

core/src/plugins/discoverable_tests.rs源码 ↗
testtest execution

这个文件专门测试插件推荐列表。所谓“可发现插件”,可以理解成商店货架上能被推荐给用户的插件。测试会临时造一个假的 Codex 主目录,写入配置文件,再放入假的 curated marketplace(官方精选插件清单),然后调用真正的插件发现逻辑看结果是否符合预期。它覆盖了几个关键情况:插件功能关掉时必须返回空列表;配置里禁用的插件不能出现;配置里指定可发现的插件要能出现;远程插件要先从服务器拉取并缓存,之后还要结合“已安装插件市场”的缓存,才会进入推荐列表。文件里还用 wiremock 搭了一个假的网络服务器,模拟 ChatGPT 后端返回远程插件数据,这样测试不用真的访问外网,也能验证缓存和过滤规则是否正确。

函数细节7
list_discoverable_plugins14–20 ↗
async fn list_discoverable_plugins(
    config: &crate::config::Config,
    loaded_plugin_app_connector_ids: &[String],
) -> anyhow::Result<Vec<DiscoverablePluginInfo>>

作用:这是测试里的便捷入口,用来在“没有登录凭证”的情况下列出可发现插件。测试用它少写一些重复代码。

数据流:输入是配置对象和当前已经加载的插件连接器 ID 列表;它把登录信息固定为空,然后转交给带认证参数的版本;最后返回插件发现逻辑给出的插件列表,自己不改配置也不写文件。

调用关系:几个普通测试会直接调用它,比如测试插件功能关闭、禁用推荐、配置指定推荐插件。它本身不做真正判断,只是把活儿交给 list_discoverable_plugins_with_auth。

调用图:调用 1 个内部函数(list_discoverable_plugins_with_auth);被 3 处调用(list_tool_suggest_discoverable_plugins_includes_configured_plugin_ids, list_tool_suggest_discoverable_plugins_omits_disabled_tool_suggestions, list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled)。

list_discoverable_plugins_with_auth22–39 ↗
async fn list_discoverable_plugins_with_auth(
    config: &crate::config::Config,
    auth: Option<&codex_login::CodexAuth>,
    loaded_plugin_app_connector_ids: &[String],
) -> anyhow::Result<Vec<Dis

作用:这是带登录信息的测试辅助函数。它会先创建一个 PluginsManager,也就是插件系统的“办事员”,再去查询可发现插件。

数据流:输入是配置、可选的登录凭证、已加载的连接器 ID;它根据配置里的 codex_home 和产品类型创建 PluginsManager,并从登录凭证里取出 API 认证模式;然后把配置、管理器和认证信息一起交给下一层;输出是整理后的可发现插件列表。

调用关系:list_discoverable_plugins 会调用它。它负责准备 PluginsManager,然后继续调用 list_discoverable_plugins_with_manager_and_auth,让测试可以复用同一套发现流程。

调用图:调用 2 个内部函数(new_with_options, list_discoverable_plugins_with_manager_and_auth);被 1 处调用(list_discoverable_plugins)。

list_discoverable_plugins_with_manager_and_auth41–54 ↗
async fn list_discoverable_plugins_with_manager_and_auth(
    config: &crate::config::Config,
    plugins_manager: &PluginsManager,
    auth: Option<&codex_login::CodexAuth>,
    loaded_plugin_app_con

作用:这是最贴近被测功能的包装函数。它把测试准备好的配置、插件管理器和认证信息,直接送进真正的插件推荐列表函数。

数据流:输入是配置、PluginsManager、可选登录凭证和已加载连接器 ID;它不加工数据,只调用 super::list_tool_suggest_discoverable_plugins;输出就是该真实函数返回的 DiscoverablePluginInfo 列表。

调用关系:它被 list_discoverable_plugins_with_auth 使用,也被远程插件缓存测试直接使用。这样远程测试可以先手动建立缓存,再用同一个 PluginsManager 去验证推荐结果。

调用图:被 2 处调用(list_discoverable_plugins_with_auth, list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins);外部调用 1 个(list_tool_suggest_discoverable_plugins)。

list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins57–332 ↗
async fn list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins()

作用:这个测试确认远程全局插件只有在正确缓存后,才会作为“官方精选远程插件”出现在可发现列表里,并且禁用配置能把它隐藏掉。

数据流:它先创建临时目录和配置,打开插件与远程插件功能;再启动一个假的 HTTP 服务器,模拟后端返回多个远程插件,其中有可用的 GitHub,也有不该展示的未列出、不可用或管理员禁用插件;接着用假登录凭证拉取并缓存远程插件目录。第一次查询时确认 GitHub 还不会直接出现;随后再模拟已安装插件列表为空并建立远程已安装市场缓存;第二次查询确认只出现 github@openai-curated-remote,并检查名称、描述、技能和连接器 ID 都正确;最后写入禁用配置,再确认这个远程插件被过滤掉。

调用关系:这是本文件最完整的集成式测试。它调用远程目录缓存函数 fetch_and_cache_global_remote_plugin_catalog,也调用 list_discoverable_plugins_with_manager_and_auth 去走真实发现逻辑,还用 wiremock 接住网络请求,避免测试依赖真实服务器。

调用图:调用 4 个内部函数(new, fetch_and_cache_global_remote_plugin_catalog, list_discoverable_plugins_with_manager_and_auth, create_dummy_chatgpt_auth_for_testing);外部调用 10 个(given, start, new, assert!, assert_eq!, load_plugins_config, write_file, format!, json!, tempdir)。

list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled335–350 ↗
async fn list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled()

作用:这个测试确认只要插件总开关关掉,就算本地有官方精选插件清单,也不能推荐任何插件。

数据流:它创建临时 Codex 主目录,写入一个包含 slack 的官方精选插件清单,再写配置把 features.plugins 设为 false;然后加载配置并调用 list_discoverable_plugins;最后断言返回的是空列表。

调用关系:它使用 curated_plugins_repo_path 找到测试用的官方精选插件目录,用 write_openai_curated_marketplace 写假数据,再通过 list_discoverable_plugins 进入被测流程。它验证的是最外层功能开关的拦截效果。

调用图:调用 2 个内部函数(curated_plugins_repo_path, list_discoverable_plugins);外部调用 5 个(assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, tempdir)。

list_tool_suggest_discoverable_plugins_omits_disabled_tool_suggestions353–373 ↗
async fn list_tool_suggest_discoverable_plugins_omits_disabled_tool_suggestions()

作用:这个测试确认用户在配置里明确禁用某个插件推荐后,这个插件不会再出现在推荐列表里。

数据流:它创建临时目录,放入一个 slack 官方精选插件;配置里打开插件功能,同时在 tool_suggest.disabled_tools 里写入 slack@openai-curated;加载配置后查询可发现插件;最后检查结果是空列表,说明 slack 被成功过滤。

调用关系:它通过 list_discoverable_plugins 走默认无认证路径。它和“插件功能关闭”的测试很像,但这里验证的不是总开关,而是单个工具推荐的黑名单规则。

调用图:调用 2 个内部函数(curated_plugins_repo_path, list_discoverable_plugins);外部调用 5 个(assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, tempdir)。

list_tool_suggest_discoverable_plugins_includes_configured_plugin_ids376–407 ↗
async fn list_tool_suggest_discoverable_plugins_includes_configured_plugin_ids()

作用:这个测试确认配置里明确写进 discoverables 的插件,会被加入可发现列表,并且它的详细信息能正确读出来。

数据流:它创建临时目录,写入一个名为 sample 的官方精选插件清单;配置里打开插件功能,并把 sample@openai-curated 放进 tool_suggest.discoverables;加载配置后查询可发现插件;最后断言返回的插件包含正确的 ID、名称、说明、是否有技能、MCP 服务器名和应用连接器 ID。MCP 服务器可以理解成插件额外提供工具能力的本地或远程服务。

调用关系:它调用 list_discoverable_plugins 进入真实发现流程,用 assert_eq! 对完整结果做精确比较。这个测试保证配置文件里的“我要推荐这个插件”不会被忽略。

调用图:调用 2 个内部函数(curated_plugins_repo_path, list_discoverable_plugins);外部调用 5 个(assert_eq!, load_plugins_config, write_file, write_openai_curated_marketplace, tempdir)。

core/src/plugins/mentions_tests.rs源码 ↗
testtest

用户输入里可能有两种“提到某个东西”的方式:一种是结构化的 Mention,像表单里已经标好的条目;另一种是文本里的 Markdown 链接,比如 @sample(plugin://sample@test)。这个测试文件就像一组验收题,检查插件系统能不能从这些输入里准确挑出 app:// 开头的应用编号,以及 plugin:// 开头、并且写法正确的插件提及。它还特别检查去重:同一个应用或插件既在文本里出现,又在结构化 Mention 里出现时,只应该算一次。另一个重点是排除误判,比如 mcp://、skill://、普通文件路径,或者用 $sample 这种应用风格写出来的插件链接,都不该被当成插件。文件里的 text_input 和 plugin 是小工具,用来快速造测试数据;各个 test 函数则把输入喂给真正的收集函数,再用断言确认结果和预期一致。

函数细节10
text_input10–15 ↗
fn text_input(text: &str) -> UserInput

作用:这是测试用的小帮手,用一段普通文字快速做出一个 UserInput::Text。测试里不用每次手写一大段结构体,读起来更清楚。

数据流:进去一段字符串 → 它把字符串复制成 UserInput::Text 里的 text 字段,并把 text_elements 设为空列表 → 出来一个代表“用户输入了一段文字”的测试对象。

调用关系:它服务于后面几个插件提及测试。那些测试需要模拟“用户在文字里写了链接”,就先调用 text_input 造出文本输入,再交给 collect_explicit_plugin_mentions 去识别。它内部只用到字符串创建这类基础能力。

调用图:被 4 处调用(collect_explicit_plugin_mentions_dedupes_structured_and_linked_mentions, collect_explicit_plugin_mentions_from_linked_text_mentions, collect_explicit_plugin_mentions_ignores_dollar_linked_plugin_mentions, collect_explicit_plugin_mentions_ignores_non_plugin_paths);外部调用 1 个(new)。

plugin17–26 ↗
fn plugin(config_name: &str, display_name: &str) -> PluginCapabilitySummary

作用:这是测试用的小帮手,用配置名和显示名快速做出一个插件摘要。这样测试可以专注看“有没有识别到正确插件”,不用每次填一堆无关字段。

数据流:进去插件的 config_name 和 display_name → 它把这两个名字放进 PluginCapabilitySummary,并给其他字段填上测试用的默认值,比如没有描述、有技能、没有服务器名和应用连接编号 → 出来一个可用于比较的插件摘要对象。

调用关系:它给插件提及相关测试准备候选插件列表和预期结果。真正被测的是 collect_explicit_plugin_mentions;plugin 只是让这些测试数据更短、更容易看懂。

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

collect_explicit_app_ids_from_linked_text_mentions29–35 ↗
fn collect_explicit_app_ids_from_linked_text_mentions()

作用:这个测试确认:如果用户在普通文字里写了 app://calendar 这种应用链接,系统能识别出 calendar 这个应用编号。

数据流:进去一段包含 Markdown 链接的文本输入 → 测试把它交给 collect_explicit_app_ids → 出来应该是只包含 calendar 的集合;如果不是,assert_eq! 会让测试失败。

调用关系:这是对 collect_explicit_app_ids 的基础场景检查。它模拟用户直接在文字里点名一个应用,确认收集函数能从链接里把应用编号抠出来。

调用图:外部调用 3 个(assert_eq!, collect_explicit_app_ids, vec!)。

collect_explicit_app_ids_dedupes_structured_and_linked_mentions38–50 ↗
fn collect_explicit_app_ids_dedupes_structured_and_linked_mentions()

作用:这个测试确认:同一个应用同时以文字链接和结构化 Mention 出现时,只算一次。这样后续系统不会重复处理同一个应用。

数据流:进去两个输入:一个文本里的 app://calendar 链接,一个结构化的 app://calendar Mention → collect_explicit_app_ids 收集应用编号并去重 → 出来应该仍然只有一个 calendar。

调用关系:它测试 collect_explicit_app_ids 的去重能力。场景像用户界面一边保留了结构化提及,一边文本里也带着链接;函数必须把两者合并成一个干净结果。

调用图:外部调用 3 个(assert_eq!, collect_explicit_app_ids, vec!)。

collect_explicit_app_ids_ignores_non_app_paths53–75 ↗
fn collect_explicit_app_ids_ignores_non_app_paths()

作用:这个测试确认:不是 app:// 开头的路径不会被当成应用。它防止系统把文档、技能或本地文件误认成应用。

数据流:进去一批非应用路径,包括 mcp://、skill:// 和普通文件路径,既有文本链接也有结构化 Mention → collect_explicit_app_ids 尝试收集应用编号 → 出来应该是空集合,表示没有发现明确应用。

调用关系:它守住 collect_explicit_app_ids 的边界。这个函数只该识别应用链接;其他类型的资源要留给别的逻辑处理,不能在这里混进来。

调用图:外部调用 3 个(assert_eq!, collect_explicit_app_ids, vec!)。

collect_explicit_plugin_mentions_from_structured_paths78–93 ↗
fn collect_explicit_plugin_mentions_from_structured_paths()

作用:这个测试确认:如果用户输入里已经有结构化的 plugin://sample@test Mention,系统能在已知插件列表里找到对应插件。

数据流:进去一个结构化插件 Mention 和两个候选插件摘要 → collect_explicit_plugin_mentions 按路径里的插件配置名去匹配 → 出来应该只包含 sample@test 这个插件摘要。

调用关系:它测试 collect_explicit_plugin_mentions 对结构化 Mention 的支持。结构化 Mention 像是界面提前贴好的标签,被测函数需要把这个标签转成真正可用的插件信息。

调用图:外部调用 3 个(assert_eq!, collect_explicit_plugin_mentions, vec!)。

collect_explicit_plugin_mentions_from_linked_text_mentions96–108 ↗
fn collect_explicit_plugin_mentions_from_linked_text_mentions()

作用:这个测试确认:如果用户在文字里写 @sample(plugin://sample@test),系统能把它识别成明确提到 sample 插件。

数据流:进去一段由 text_input 造出的文本输入,以及候选插件列表 → collect_explicit_plugin_mentions 扫描文字里的 plugin:// 链接并匹配插件 → 出来应该是 sample@test 对应的插件摘要。

调用关系:它先用 text_input 模拟用户打字,再把输入交给 collect_explicit_plugin_mentions。这个场景覆盖的是“插件提及藏在普通文本链接里”的路径。

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

collect_explicit_plugin_mentions_dedupes_structured_and_linked_mentions111–129 ↗
fn collect_explicit_plugin_mentions_dedupes_structured_and_linked_mentions()

作用:这个测试确认:同一个插件既在文字链接里出现,又在结构化 Mention 里出现时,只返回一次。这样后续不会重复启用或展示同一个插件。

数据流:进去两个指向 sample@test 的提及:一个文本链接,一个结构化 Mention,再加上候选插件列表 → collect_explicit_plugin_mentions 收集并去重 → 出来应该只有一个 sample@test 插件摘要。

调用关系:它用 text_input 造文本提及,同时手写结构化 Mention,然后一起交给 collect_explicit_plugin_mentions。它验证这个收集函数能把不同来源的同一插件合并掉。

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

collect_explicit_plugin_mentions_ignores_non_plugin_paths132–143 ↗
fn collect_explicit_plugin_mentions_ignores_non_plugin_paths()

作用:这个测试确认:应用链接、技能链接和普通文件路径不会被当成插件。它防止插件识别逻辑太贪心,见到链接就乱匹配。

数据流:进去一段包含 app://、skill:// 和文件路径的文本输入,以及一个候选插件 → collect_explicit_plugin_mentions 扫描输入 → 出来应该是空列表,表示没有明确插件提及。

调用关系:它通过 text_input 模拟一段混杂链接的用户文字,再交给 collect_explicit_plugin_mentions。这个测试确保插件收集函数只关心 plugin:// 这类插件路径。

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

collect_explicit_plugin_mentions_ignores_dollar_linked_plugin_mentions146–155 ↗
fn collect_explicit_plugin_mentions_ignores_dollar_linked_plugin_mentions()

作用:这个测试确认:即使链接地址是 plugin://sample@test,如果显示文字写成 $sample,也不算插件提及。这里的 $ 更像应用提及的写法,而插件提及要求 @ 这种写法。

数据流:进去一段文本 use $sample(plugin://sample@test) 和候选插件列表 → collect_explicit_plugin_mentions 检查链接和显示写法 → 出来应该是空列表,因为这种写法不符合插件提及规则。

调用关系:它用 text_input 造出一个容易误判的文本链接,再交给 collect_explicit_plugin_mentions。这个测试补上一个细节规则:插件不仅要有 plugin:// 路径,还要用正确的 @ 风格提及。

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

core/src/plugins/render_tests.rs源码 ↗
testtest run

这是一组自动测试。可以把它理解成给插件说明功能写的“验收清单”:当系统准备把插件相关提示塞进最终说明里时,必须符合这里规定的样子。第一个测试确认:如果当前没有任何插件,生成函数应该返回空,也就是不要凭空加一段插件说明。第二个测试确认:只要有插件存在,就会生成一段固定的插件使用指南,告诉模型插件是什么、技能和 MCP 工具该怎么命名、用户点名插件时该怎么偏向使用等。这里的 MCP 可以理解成一种让模型调用外部工具的接口规范。一个重要点是:测试明确要求不要在这段文字里列出具体插件,只保留通用使用规则。这样能避免提示内容过度暴露或变得臃肿。

函数细节2
render_plugins_section_returns_none_for_empty_plugins5–7 ↗
fn render_plugins_section_returns_none_for_empty_plugins()

作用:这个测试检查“没有插件”这个场景。它确保生成插件说明的函数在拿到空列表时,结果就是没有内容,而不是输出一段没必要的提示。

数据流:输入是一个空的插件列表 → 测试把它交给 render_plugins_section → 期望得到 None,也就是“没有可渲染内容” → 如果结果不是 None,测试就失败,提醒开发者这个行为被改坏了。

调用关系:它是插件说明渲染功能的边界测试,专门守住最简单也最容易被忽略的情况。它调用外部的 assert_eq! 来比较实际结果和期望结果。

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

render_plugins_section_keeps_plugin_usage_guidance_without_listing_plugins10–23 ↗
fn render_plugins_section_keeps_plugin_usage_guidance_without_listing_plugins()

作用:这个测试检查“有插件”时生成的说明是否正确。它确认系统会输出固定的插件使用指南,但不会把具体插件逐个列出来。

数据流:输入是一个示例插件信息,里面有配置名、显示名、描述,并标记它带有技能;其他字段用默认值补齐 → 测试调用 render_plugins_section 生成文本 → 如果生成失败就直接报错 → 然后把生成文本和一整段预期说明逐字比较 → 结果必须完全一致。

调用关系:它验证插件说明文本的核心格式和内容,是防止提示词被意外改动的保护网。它用 default 填充不关心的插件字段,并用 assert_eq! 检查最终字符串是否和预期完全相同。

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

core/src/connectors_tests.rs源码 ↗
testtest run

这里的“连接器”可以理解成 Codex 连接外部服务的小插头,比如日历、网盘、邮件。这个测试文件会造一些假的配置文件、假的工具列表、假的登录信息,然后检查系统能不能正确判断:哪些连接器可用、它们显示什么名字和描述、插件名有没有去重、缓存里写的是不是最新结果、用户审批规则有没有按优先级生效、工具推荐列表有没有排除被禁用的项目。它还特别测试企业配置的限制:就算本地配置想让某个应用走人工审核,如果企业只允许自动审核,系统也必须听企业规则。整体上,这个文件像一套体检表,保证连接器从“被发现”到“显示给用户”再到“按配置启用或禁用”的过程不会悄悄出错。

函数细节16
app26–42 ↗
fn app(id: &str) -> AppInfo

作用:这个小工具函数用一个 id 快速造出一个默认的 AppInfo,也就是“一个应用/连接器的信息卡片”。测试里不想每次都手写一大堆字段,所以用它来省事。

数据流:进去的是一个应用 id 字符串 → 它把这个 id 同时填到应用的 id 和 name 里,其他说明、图标、安装地址等字段先留空,并默认设成“已启用但还不可访问” → 出来的是一张完整但很朴素的 AppInfo 测试数据。

调用关系:它主要服务于测试 with_app_enabled_state_preserves_unrelated_disabled_connector。那个测试需要几个应用样本来检查启用/禁用状态是否被正确保留,所以先让 app 生成基础对象,再按需要改其中的字段。

调用图:被 1 处调用(with_app_enabled_state_preserves_unrelated_disabled_connector);外部调用 1 个(new)。

plugin_names44–46 ↗
fn plugin_names(names: &[&str]) -> Vec<String>

作用:这个小工具函数把一组文本形式的插件名字转换成系统真正使用的 String 列表。它是测试数据的“格式转换器”。

数据流:进去的是字符串切片,比如 ["beta", "sample"] → 它逐个复制成拥有所有权的字符串 → 出来的是 Vec<String>,可以直接塞进 ToolInfo 或 AppInfo 的 plugin_display_names 字段。

调用关系:它被 codex_app_tool 调用。codex_app_tool 在制造假的 Codex 应用工具时,需要同时填好插件显示名,所以把这一步交给 plugin_names。

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

test_tool_definition48–50 ↗
fn test_tool_definition(tool_name: &str) -> Tool

作用:这个函数造一个最简单的假工具定义。测试只关心工具叫什么,不关心工具真正的参数结构,所以这里用空的 JSON 对象当占位。

数据流:进去的是工具名 → 它创建一个空的 JsonObject,也就是空的 JSON 参数说明,再用这个名字和空参数说明构造 Tool → 出来的是一个可以放进 ToolInfo 的 Tool 测试对象。

调用关系:它被 codex_app_tool 调用。codex_app_tool 负责拼出完整的假工具信息,而真正的 Tool 字段由 test_tool_definition 来填。

调用图:被 1 处调用(codex_app_tool);外部调用 3 个(new, default, new_with_raw)。

codex_app_tool52–75 ↗
fn codex_app_tool(
    tool_name: &str,
    connector_id: &str,
    connector_name: Option<&str>,
    plugin_display_names: &[&str],
) -> ToolInfo

作用:这个函数专门造一个“来自 Codex Apps MCP 服务器的工具”。MCP 可以简单理解成让模型调用外部工具的一套接口,这里用假数据模拟真实连接器暴露出来的工具。

数据流:进去的是工具名、连接器 id、可选的连接器名称、插件显示名列表 → 它先根据连接器名称生成工具命名空间,没有名称时就用默认服务器名;再创建 ToolInfo,把连接器 id、连接器名称、插件显示名和假 Tool 都填进去 → 出来的是一条完整的 ToolInfo 测试数据。

调用关系:多个测试用它快速准备输入数据,尤其是检查 accessible_connectors_from_mcp_tools 和缓存刷新时。它内部把插件名转换交给 plugin_names,把工具定义创建交给 test_tool_definition。

调用图:调用 2 个内部函数(plugin_names, test_tool_definition)。

with_accessible_connectors_cache_cleared77–90 ↗
fn with_accessible_connectors_cache_cleared(f: impl FnOnce() -> R) -> R

作用:这个函数临时清空“可访问连接器”的全局缓存,跑完一段测试代码后再把原缓存放回去。这样测试不会被之前的缓存结果污染。

数据流:进去的是一段要执行的函数 f → 它先拿到缓存锁,保存旧缓存并清空;然后执行 f;最后再次拿锁,把旧缓存恢复 → 出来的是 f 的返回值,同时全局缓存回到测试前的状态。

调用关系:它被 refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_installed_apps 使用。那个测试要确认刷新缓存真的写入了最新应用,如果不先清空缓存,旧数据可能让测试结果不可靠。

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

accessible_connectors_from_mcp_tools_carries_plugin_display_names93–141 ↗
fn accessible_connectors_from_mcp_tools_carries_plugin_display_names()

作用:这个测试确认:从 MCP 工具列表整理出连接器列表时,插件显示名会被正确带到应用信息里,并且重复名字会被整理掉。

数据流:进去的是测试里手工创建的三条工具信息:两条属于 calendar 连接器,一条普通 sample 工具不属于连接器 → 被测逻辑会只挑出真正有 connector_id 的 Codex Apps 工具,合并成一个 calendar 应用,并使用更完整的连接器名称和插件显示名 → 最后用断言检查输出是否正好是一条 Google Calendar 应用信息。

调用关系:这是对 accessible_connectors_from_mcp_tools 这条整理流程的直接检查。它通过 codex_app_tool 准备输入,并用 assert_eq! 比较实际结果和期望结果。

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

refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_installed_apps144–208 ↗
async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_installed_apps()

作用:这个测试确认:系统从当前 MCP 工具刷新“可访问连接器缓存”时,缓存里会写入最新安装或可访问的应用列表。

数据流:进去的是一个临时的 Codex 主目录、一份开启 Apps 功能的配置、以及两条假的 Codex Apps 工具 → 测试先算出缓存键,再清空全局缓存,调用刷新函数写缓存,随后读取缓存内容 → 出来的是缓存里的 AppInfo 列表,测试要求它包含 calendar 和 hidden 两个连接器,并且字段都正确。

调用关系:它把缓存相关流程串起来测:先用 ConfigBuilder 建配置,再用 with_accessible_connectors_cache_cleared 隔离缓存,接着调用 refresh_accessible_connectors_cache_from_mcp_tools,最后用 read_cached_accessible_connectors 验证结果。

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

accessible_connectors_from_mcp_tools_preserves_description211–247 ↗
fn accessible_connectors_from_mcp_tools_preserves_description()

作用:这个测试确认:工具命名空间上的说明文字不会在转换成应用信息时丢掉。比如“Plan events”这样的描述应该显示在日历应用上。

数据流:进去的是一条带 namespace_description 的 calendar 工具信息 → 被测逻辑把它整理成可访问连接器 AppInfo → 出来的一条 AppInfo 必须保留 description 字段,同时安装地址、名称、可访问状态也要符合预期。

调用关系:它直接检查 accessible_connectors_from_mcp_tools 的字段保留行为。前面的插件名测试关注 plugin_display_names,这个测试补上 description 这个容易被漏掉的字段。

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

app_approvals_reviewer_uses_app_then_default_then_global250–312 ↗
async fn app_approvals_reviewer_uses_app_then_default_then_global()

作用:这个测试确认应用审批人的选择顺序是对的:先看具体应用配置,再看 apps._default 默认配置,最后才看全局配置。审批人可以理解成“工具调用前由谁来把关”。

数据流:进去的是临时写出的配置文件,里面同时设置全局 approvals_reviewer、应用默认 approvals_reviewer、calendar 单独 approvals_reviewer → ConfigBuilder 读取配置后,测试分别询问 calendar、drive、没有连接器 id、以及非 Codex Apps 服务器的审批人 → 出来的结果必须符合“应用优先、默认其次、全局兜底”的规则。

调用关系:它围绕 mcp_approvals_reviewer 展开。测试通过写配置文件制造不同层级的规则,再用 assert_eq! 检查该函数在不同服务器名和连接器 id 下选出的值。

调用图:外部调用 5 个(assert_eq!, default, format!, write, tempdir)。

default_app_approvals_reviewer_respects_global_reviewer_requirements315–342 ↗
async fn default_app_approvals_reviewer_respects_global_reviewer_requirements()

作用:这个测试确认:即使 apps._default 里写了某种审批人,如果企业规则不允许,系统也不能采用它。企业规则在这里是更高层级的安全限制。

数据流:进去的是本地配置:全局是 auto_review,应用默认是 user;同时云端企业要求只允许 auto_review → 构建配置时会把本地配置和企业要求合并 → 出来的 mcp_approvals_reviewer 结果必须是 AutoReview,而不是本地默认写的 User。

调用关系:它测试 mcp_approvals_reviewer 和配置加载流程的配合。企业限制由 CloudConfigBundleFixture::loader_with_enterprise_requirement 提供,ConfigBuilder 负责加载,最后断言审批结果被企业规则纠正。

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

app_approvals_reviewer_respects_global_reviewer_requirements345–372 ↗
async fn app_approvals_reviewer_respects_global_reviewer_requirements()

作用:这个测试和上一个类似,但它检查的是“某个具体应用”的审批配置也必须服从企业限制。也就是说,单个应用不能绕过全局安全要求。

数据流:进去的是本地配置:全局 auto_review,calendar 单独写 user;同时企业要求只允许 auto_review → 配置构建后,被测函数查询 calendar 的审批人 → 出来的结果必须仍然是 AutoReview。

调用关系:它继续验证 mcp_approvals_reviewer 在企业约束下的行为。CloudConfigBundleFixture 提供企业要求,ConfigBuilder 合并配置,测试用 assert_eq! 确认具体应用配置不会突破限制。

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

with_app_enabled_state_preserves_unrelated_disabled_connector375–410 ↗
async fn with_app_enabled_state_preserves_unrelated_disabled_connector()

作用:这个测试确认:给应用列表套用启用/禁用状态时,不会误伤别的已经禁用的连接器。简单说,改 drive 的配置时,不应该把 slack 的禁用状态弄丢。

数据流:进去的是临时配置和一份要求:connector_drive 被配置成 disabled;测试又准备了一个本来就 disabled 的 connector_slack,以及一个默认状态的 connector_drive → 被测函数 with_app_enabled_state 根据配置修正列表 → 出来的列表里 slack 仍然是 disabled,drive 也被设成 disabled。

调用关系:它用 app 函数快速生成 AppInfo,再手动设置状态。配置要求通过 ConfigLayerStack 和 ConfigRequirementsToml 放进 config,最后调用 with_app_enabled_state 并比较结果。

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

tool_suggest_connector_ids_include_configured_tool_suggest_discoverables413–437 ↗
async fn tool_suggest_connector_ids_include_configured_tool_suggest_discoverables()

作用:这个测试确认:配置文件里写进 tool_suggest.discoverables 的连接器 id,会被工具推荐系统识别出来。空白 id 和非连接器类型不会混进去。

数据流:进去的是一份临时配置,里面有一个 connector、一个 plugin、一个只有空格的 connector → ConfigBuilder 读取配置后,tool_suggest_connector_ids 提取可推荐的连接器 id → 出来的集合只包含那个有效的 connector id。

调用关系:它直接检查 tool_suggest_connector_ids 的过滤规则。测试通过写配置制造混合输入,再用 assert_eq! 确认只留下有效连接器。

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

tool_suggest_connector_ids_exclude_disabled_tool_suggestions440–466 ↗
async fn tool_suggest_connector_ids_exclude_disabled_tool_suggestions()

作用:这个测试确认:如果某个推荐连接器同时出现在 disabled_tools 里,它就不能再被推荐。也就是“黑名单”会覆盖“可发现列表”。

数据流:进去的是配置:calendar 和 gmail 都在 discoverables 里,但 calendar 又在 disabled_tools 里 → 被测函数读取后先找出可推荐项,再排除被禁用项 → 出来的集合只剩 connector_gmail。

调用关系:它验证 tool_suggest_connector_ids 在推荐和禁用两张列表同时存在时的优先级。测试用临时配置文件提供输入,用断言检查过滤后的集合。

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

tool_suggest_uses_connector_id_fallback_when_directory_cache_is_empty469–508 ↗
async fn tool_suggest_uses_connector_id_fallback_when_directory_cache_is_empty()

作用:这个测试确认:当连接器目录缓存是空的,但配置里已经写了连接器 id,工具推荐仍然能显示一个基于 id 生成的应用信息。这样推荐功能不会因为目录缓存缺失就完全没内容。

数据流:进去的是开启 Apps 功能的配置、一个 tool_suggest 连接器 id、假的 ChatGPT 登录信息、空的已知工具和空的已加载插件应用列表 → 被测异步函数尝试列出可推荐工具,目录缓存为空时用 connector id 兜底生成 AppInfo → 出来的是一个 DiscoverableTool,内容来自 plugin_connector_to_app_info 生成的 gmail 连接器信息。

调用关系:它测试 list_tool_suggest_discoverable_tools_with_auth 的兜底路径。PluginsManager 提供插件目录环境,CodexAuth 提供假登录身份,最终结果和 plugin_connector_to_app_info 生成的期望对象比较。

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

tool_suggest_includes_connectors_from_loaded_plugin_apps511–546 ↗
async fn tool_suggest_includes_connectors_from_loaded_plugin_apps()

作用:这个测试确认:已经加载进来的插件应用连接器,也会进入工具推荐列表。也就是说,不只配置文件里的 discoverables 能被推荐,运行时已加载的插件应用也能被看见。

数据流:进去的是开启 Apps 功能的配置、假的登录信息、一个 loaded_plugin_app_connector_ids 列表,其中包含 databricks workspace 的连接器 id → 被测函数把这个已加载连接器转成可推荐工具 → 出来的是包含该连接器的 DiscoverableTool 列表。

调用关系:它覆盖 list_tool_suggest_discoverable_tools_with_auth 的另一条输入来源:已加载插件应用。PluginsManager 和假 auth 搭好环境,loaded_plugin_app_connector_ids 提供运行时数据,最后用 assert_eq! 验证输出。

调用图:调用 2 个内部函数(new, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(assert_eq!, default, write, tempdir, vec!)。

tools/src/request_plugin_install_tests.rs源码 ↗
testtest run

这份文件不是真正给用户发请求的代码,而是像质检员一样,专门检查相关代码产出的结果对不对。这里的“插件”和“连接器”可以理解成外部能力:比如 Gmail、Google Calendar,系统要先请用户同意安装或启用。测试会手工造出一些假的工具信息,再调用生成安装请求的函数,然后把结果和一份“标准答案”逐字比较。它还检查两个重要判断:某个连接器是不是已经变成可访问状态,以及一组被要求的连接器是不是全部都已经准备好。这样做很重要,因为这类请求通常会跨过不同系统传递,字段少一个、名字错一个,另一边就可能看不懂。

函数细节5
build_request_plugin_install_elicitation_request_uses_expected_shape7–73 ↗
fn build_request_plugin_install_elicitation_request_uses_expected_shape()

作用:这个测试确认:当系统建议用户安装一个连接器时,生成出来的“请用户确认安装”的表单请求长得完全符合预期。这里的连接器可以理解成连接外部服务的入口,比如 Google Calendar。

数据流:进去的是一份假的安装参数、一份假的 Google Calendar 连接器信息、线程编号和回合编号 → 测试调用生成请求的函数,把这些信息拼成一份用户确认表单 → 出来的是一个请求对象,测试用 assert_eq! 把它和手写的标准对象比较,确保消息、工具名、安装地址、元数据等都没错。

调用关系:这个函数由 Rust 的测试运行器在跑测试时调用。它自己先准备测试材料,再检查生成请求的函数是否把连接器信息正确塞进 McpServerElicitationRequestParams 里;最后把结果交给 assert_eq! 做严格比对。

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

build_request_plugin_install_elicitation_request_injects_plugin_metadata76–131 ↗
fn build_request_plugin_install_elicitation_request_injects_plugin_metadata()

作用:这个测试确认:当要安装的是插件而不是普通连接器时,请求里会带上插件特有的信息。比如远程插件编号,以及这个插件关联了哪些应用连接器。

数据流:进去的是一份假的插件安装参数和插件信息,里面包含插件名、远程插件 ID、MCP 服务器名和关联连接器 ID → 测试调用生成安装确认请求的函数 → 出来的是一份表单请求,测试检查它的元数据里不仅有通用字段,也正确包含 remote_plugin_id 和 app_connector_ids。

调用关系:这个函数同样由测试运行器调用。它覆盖的是插件路径,和连接器测试互补;测试中会构造 DiscoverableTool::Plugin,然后用 assert_eq! 验证生成函数没有漏掉插件专属字段。

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

build_request_plugin_install_meta_uses_expected_shape134–176 ↗
fn build_request_plugin_install_meta_uses_expected_shape()

作用:这个测试专门检查安装请求里的“元数据”长什么样。元数据就是藏在请求里的说明卡片,告诉接收方这是哪种批准、要安装什么、原因是什么。

数据流:进去的是连接器类型、安装动作、推荐理由,以及一份假的 Gmail 连接器信息 → 测试调用 build_request_plugin_install_meta 生成元数据 → 出来的是 RequestPluginInstallMeta,测试把它和标准答案比较,确认工具 ID、工具名、安装链接、持久化标记等字段都正确。

调用关系:这个测试比完整请求测试更聚焦,只盯住 build_request_plugin_install_meta 的输出。完整请求构建通常也会依赖这类元数据,所以这里相当于单独检查机器里的一个关键小零件。

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

verified_connector_install_completed_requires_accessible_connector179–204 ↗
fn verified_connector_install_completed_requires_accessible_connector()

作用:这个测试确认:只有当某个连接器真的出现在“可访问”的连接器列表里时,系统才认为安装完成。不能只因为名字被提到过,就误判为已经装好了。

数据流:进去的是一个只包含 calendar 的可访问连接器列表 → 测试分别询问 calendar 和 gmail 是否安装完成 → 对 calendar 期望得到 true,对 gmail 期望得到 false,确保函数按连接器 ID 查找,并且不会把不存在的连接器当成成功。

调用关系:这个函数由测试运行器调用,检查 verified_connector_install_completed 的判断规则。它用 assert! 验证两个场景:一个应该通过,一个应该失败,防止安装状态检查过于宽松。

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

all_requested_connectors_picked_up_requires_every_expected_connector207–232 ↗
fn all_requested_connectors_picked_up_requires_every_expected_connector()

作用:这个测试确认:如果一次请求里要求多个连接器,那么必须每一个都已经可访问,系统才算全部准备好。少一个都不能算完成。

数据流:进去的是一个可访问连接器列表,里面只有 calendar → 测试先要求只有 calendar,结果应该成功;再要求 calendar 加 gmail,结果应该失败 → 出来的是两个布尔判断,用来证明函数会逐个核对所有被请求的连接器。

调用关系:这个测试检查 all_requested_connectors_picked_up 的整体完成判断。它和单个连接器的测试配合:前者看“一批是否都到齐”,后者看“某一个是否到位”。

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

技能加载与扩展行为

这些测试覆盖从核心加载器和管理器到支持执行器及扩展提供的技能目录中的技能解析和调用。

core-skills/src/injection_tests.rs源码 ↗
testtest

这里测试的是“技能注入”这件事:用户可以写 $alpha-skill,或者写成带路径的链接 [$alpha-skill](/tmp/alpha),系统要从输入里找出用户明确想用哪些技能。文件先用几个小帮手造出假的技能、假的路径和期望结果,然后分两大类测试:一类检查文字解析,比如名字边界要精确、不能把 $PATH 这种常见环境变量当技能、链接路径可以有空格;另一类检查选择规则,比如结构化输入优先、同名技能不乱选、路径明确时按路径选、禁用或不存在的路径不能回退乱配。它像给门卫列了一堆验票场景,确保只有真正有效、明确、没被禁用的“票”才能进门。

函数细节27
make_skill9–21 ↗
fn make_skill(name: &str, path: &str) -> SkillMetadata

作用:造一个测试用的技能资料。测试不想每次都手写一大堆字段,所以用它快速做出一个名字和路径都可控的假技能。

数据流:进去的是技能名和一个路径字符串;它把名字变成描述文字,把路径转成测试用的绝对路径,并填好其他默认字段;出来的是一个完整的 SkillMetadata,也就是一份技能档案。

调用关系:很多选择技能的测试都会先调用它准备样本数据。它内部借助 test_path_buf 造测试路径,也用 format! 拼出描述文字,然后把结果交给 collect_mentions 相关测试使用。

调用图:被 13 处调用(collect_explicit_skill_mentions_allows_explicit_path_with_connector_conflict, collect_explicit_skill_mentions_dedupes_by_path, collect_explicit_skill_mentions_prefers_linked_path_over_name, collect_explicit_skill_mentions_prefers_resource_path, collect_explicit_skill_mentions_prioritizes_structured_inputs, collect_explicit_skill_mentions_skips_ambiguous_name, collect_explicit_skill_mentions_skips_disabled_structured_and_blocks_plain_fallback, collect_explicit_skill_mentions_skips_invalid_structured_and_blocks_plain_fallback, collect_explicit_skill_mentions_skips_missing_path_with_no_fallback, collect_explicit_skill_mentions_skips_missing_path_without_fallback (+3 more));外部调用 2 个(test_path_buf, format!)。

set23–25 ↗
fn set(items: &'a [&'a str]) -> HashSet<&'a str>

作用:把一串字符串变成集合,方便比较“有哪些名字”而不关心顺序。集合可以理解成一个不重复的名单。

数据流:进去的是字符串数组;它逐个取出里面的字符串引用并收进 HashSet;出来的是一个去重后的集合。

调用关系:它主要服务于 assert_mentions。assert_mentions 要比较解析结果和期望结果时,用它把期望名单变成集合,避免因为顺序不同导致测试误报。

assert_mentions27–31 ↗
fn assert_mentions(text: &str, expected_names: &[&str], expected_paths: &[&str])

作用:检查一段文字里解析出来的技能名和路径,是否正好等于测试预期。它是多个解析测试共用的断言小工具。

数据流:进去的是待解析文字、期望的技能名列表、期望的路径列表;它调用 extract_tool_mentions 从文字中抽取结果,再把结果和期望集合比较;如果不一致,测试就失败。

调用关系:多条 extract_tool_mentions 开头的测试都会把具体例子交给它。它把重复的“解析再比较”步骤集中起来,让每个测试只关心场景本身。

调用图:被 6 处调用(extract_tool_mentions_handles_plain_and_linked_mentions, extract_tool_mentions_keeps_plugin_skill_namespaces, extract_tool_mentions_requires_link_syntax, extract_tool_mentions_skips_common_env_vars, extract_tool_mentions_stops_at_non_name_chars, extract_tool_mentions_trims_linked_paths_and_allows_spacing);外部调用 1 个(assert_eq!)。

linked_skill_mention33–35 ↗
fn linked_skill_mention(name: &str, unix_path: &str) -> String

作用:生成一种带路径的技能提法,比如 [$alpha](/tmp/alpha)。这种写法比只写名字更明确,因为它直接指出技能文件在哪里。

数据流:进去的是技能名和 Unix 风格路径;它把路径变成测试路径显示形式,再拼成 Markdown 链接字符串;出来的是一段可放进用户输入里的技能链接文字。

调用关系:需要测试“按路径选技能”的场景会用它造输入文本。它配合 make_skill 产生的假技能,让 collect_mentions 能验证链接路径优先、去重、缺失路径等规则。

调用图:被 1 处调用(collect_explicit_skill_mentions_dedupes_by_path);外部调用 1 个(format!)。

collect_mentions37–44 ↗
fn collect_mentions(
    inputs: &[UserInput],
    skills: &[SkillMetadata],
    disabled_paths: &HashSet<AbsolutePathBuf>,
    connector_slug_counts: &HashMap<String, usize>,
) -> Vec<SkillMetadata>

作用:给真正的 collect_explicit_skill_mentions 包一层短名字,方便测试调用。它代表“从用户输入里收集被明确点名的技能”这个动作。

数据流:进去的是用户输入列表、可用技能列表、被禁用的路径集合、连接器名字计数;它直接转交给 collect_explicit_skill_mentions;出来的是系统最终认为应该选中的技能列表。

调用关系:后半部分几乎所有 collect_explicit_skill_mentions 开头的测试都会调用它。它本身不改变逻辑,只是让测试代码更短,把重点留给不同输入场景。

调用图:被 13 处调用(collect_explicit_skill_mentions_allows_explicit_path_with_connector_conflict, collect_explicit_skill_mentions_dedupes_by_path, collect_explicit_skill_mentions_prefers_linked_path_over_name, collect_explicit_skill_mentions_prefers_resource_path, collect_explicit_skill_mentions_prioritizes_structured_inputs, collect_explicit_skill_mentions_skips_ambiguous_name, collect_explicit_skill_mentions_skips_disabled_structured_and_blocks_plain_fallback, collect_explicit_skill_mentions_skips_invalid_structured_and_blocks_plain_fallback, collect_explicit_skill_mentions_skips_missing_path_with_no_fallback, collect_explicit_skill_mentions_skips_missing_path_without_fallback (+3 more))。

text_mentions_skill_requires_exact_boundary47–68 ↗
fn text_mentions_skill_requires_exact_boundary()

作用:确认 $技能名 必须在名字边界上刚好匹配,不能把更长的相似词误认为目标技能。

数据流:没有外部输入;它准备几段文字,分别包含正确提法和近似但不该匹配的提法;最后用断言确认 text_mentions_skill 返回 true 或 false 符合预期。

调用关系:这是直接测试 text_mentions_skill 的基础规则。测试运行器执行它,用来守住“不要把 $notion-research-docs 误认成 $notion-research-doc”这类边界。

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

text_mentions_skill_handles_end_boundary_and_near_misses71–78 ↗
fn text_mentions_skill_handles_end_boundary_and_near_misses()

作用:检查技能名出现在文本末尾时也能识别,同时相邻多了字母的近似写法不能误识别。

数据流:它构造三种文字:刚好在末尾、名字后面多字母、后面又出现一次正确名字;调用 text_mentions_skill 后比较结果;输出只体现在测试是否通过。

调用关系:它补充上一条边界测试,覆盖“文本结尾”和“先有误导再有正确提法”的情况,帮助保证扫描逻辑不会太早或太松。

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

text_mentions_skill_handles_many_dollars_without_looping81–85 ↗
fn text_mentions_skill_handles_many_dollars_without_looping()

作用:确认遇到一长串 $ 符号时,查找逻辑不会卡住或陷入死循环。这里测试的是健壮性,也就是坏输入下也要稳。

数据流:它先生成 256 个 $ 加上一段无关文字;把这段文字交给 text_mentions_skill;期望结果是 false,并且测试能正常结束。

调用关系:测试运行器执行它时,会间接验证 text_mentions_skill 的扫描方式安全。它用 format! 造字符串,用断言确认没有误匹配。

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

extract_tool_mentions_handles_plain_and_linked_mentions88–94 ↗
fn extract_tool_mentions_handles_plain_and_linked_mentions()

作用:确认解析器既能认普通 $alpha,也能认带路径的 [$beta](/tmp/beta)

数据流:它给出一段同时包含普通提法和链接提法的文字;assert_mentions 会解析出名字 alpha、beta,以及路径 /tmp/beta;如果少了或多了就失败。

调用关系:它通过 assert_mentions 间接测试 extract_tool_mentions,是解析功能最基本的混合场景。

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

extract_tool_mentions_skips_common_env_vars97–101 ↗
fn extract_tool_mentions_skips_common_env_vars()

作用:确认 $PATH$HOME$XDG_CONFIG_HOME 这类常见环境变量不会被当成技能名。环境变量就是操作系统里常用的配置名字。

数据流:它准备几段含环境变量和真实技能名的文字;assert_mentions 负责解析并比较;结果应该只保留 alpha 或 beta 这种技能名,忽略环境变量。

调用关系:它通过 assert_mentions 检查 extract_tool_mentions 的过滤规则,避免用户提到系统变量时误触发技能。

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

extract_tool_mentions_trims_linked_paths_and_allows_spacing111–113 ↗
fn extract_tool_mentions_trims_linked_paths_and_allows_spacing()

作用:确认链接里的路径前后有空格、链接名和括号之间有空格时,系统仍能读懂。

数据流:它输入 [$beta] ( /tmp/beta ) 这种带多余空格的文字;assert_mentions 解析后应得到名字 beta 和修剪干净的路径 /tmp/beta。

调用关系:它通过 assert_mentions 测试 extract_tool_mentions 的容错性,让用户写得不那么规整时也能被正确识别。

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

extract_tool_mentions_stops_at_non_name_chars116–122 ↗
fn extract_tool_mentions_stops_at_non_name_chars()

作用:确认技能名遇到不属于名字的字符时会停下来,同时允许下划线这种合法字符。

数据流:它输入 $alpha.skill$beta_extra;解析后应得到 alpha 和 beta_extra;点号后面的 skill 不会被并进 alpha。

调用关系:它通过 assert_mentions 检查 extract_tool_mentions 的名字切分规则,避免把标点后面的普通文字误吞进技能名。

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

extract_tool_mentions_keeps_plugin_skill_namespaces125–131 ↗
fn extract_tool_mentions_keeps_plugin_skill_namespaces()

作用:确认带插件命名空间的技能名可以保留,比如 $slack:search。这里的命名空间就是用冒号说明这个技能属于哪个插件。

数据流:它输入同时含 $slack:search$alpha 的文字;assert_mentions 检查解析结果包含 slack:search 和 alpha;没有路径输出。

调用关系:它通过 assert_mentions 保护插件技能的名字格式,确保 extract_tool_mentions 不会把冒号错误当成结束符。

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

collect_explicit_skill_mentions_text_respects_skill_order134–148 ↗
fn collect_explicit_skill_mentions_text_respects_skill_order()

作用:确认从纯文字里找到多个技能时,最终结果按已有技能列表的顺序来,而不是按文字里出现的先后。

数据流:它造出 beta、alpha 两个技能,技能列表顺序是 beta 在前;输入文字先提 alpha 再提 beta;collect_mentions 返回后,断言结果仍是 beta、alpha。

调用关系:测试运行器执行它。它使用 make_skill 准备技能,调用 collect_mentions 触发真实选择逻辑,最后用断言守住旧有排序规则。

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

collect_explicit_skill_mentions_prioritizes_structured_inputs151–170 ↗
fn collect_explicit_skill_mentions_prioritizes_structured_inputs()

作用:确认结构化输入优先于普通文字。结构化输入就是系统已经明确给出技能名和路径的输入,比用户自由文本更可靠。

数据流:它准备文字里提到 alpha,同时另一个 UserInput::Skill 明确指定 beta;collect_mentions 应先选 beta,再补上 alpha;断言检查这个顺序。

调用关系:它用 make_skill 造样本,用 collect_mentions 调用选择逻辑,验证当文本提法和结构化提法同时存在时,结构化信息排在前面。

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

collect_explicit_skill_mentions_skips_invalid_structured_and_blocks_plain_fallback173–191 ↗
fn collect_explicit_skill_mentions_skips_invalid_structured_and_blocks_plain_fallback()

作用:确认结构化输入如果指定了错误路径,系统不会回头用同一段普通文字去凑合匹配。这样可以避免明明给错了精确信息却被系统猜成另一个结果。

数据流:它造一个真实路径为 /tmp/alpha 的技能;文本提到 alpha,但结构化输入给的是 /tmp/missing;collect_mentions 返回空列表;测试确认没有 fallback,也就是没有退回普通名字匹配。

调用关系:它调用 make_skill 和 collect_mentions,专门守住“精确信息无效时不要乱猜”的安全规则。

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

collect_explicit_skill_mentions_skips_disabled_structured_and_blocks_plain_fallback194–213 ↗
fn collect_explicit_skill_mentions_skips_disabled_structured_and_blocks_plain_fallback()

作用:确认结构化输入指向的技能如果已被禁用,也不会再通过普通文字把它选回来。

数据流:它准备 alpha 技能和同一路径的禁用集合;输入里既有文字提名,也有结构化技能;collect_mentions 应返回空;断言确认禁用状态生效。

调用关系:它使用 test_path_buf 生成禁用路径,调用 collect_mentions 测试禁用规则,保证被关掉的技能不会绕路被启用。

调用图:调用 2 个内部函数(collect_mentions, make_skill);外部调用 5 个(new, from, assert_eq!, test_path_buf, vec!)。

collect_explicit_skill_mentions_dedupes_by_path216–229 ↗
fn collect_explicit_skill_mentions_dedupes_by_path()

作用:确认同一个路径的技能被提到多次时,只选一次。路径就像身份证,同一个身份证不能重复登记。

数据流:它造一个 alpha 技能,再把同一个链接提法放进文字两次;collect_mentions 最终只返回一个 alpha;断言检查没有重复项。

调用关系:它通过 linked_skill_mention 造链接输入,通过 make_skill 造技能,再交给 collect_mentions 验证按路径去重。

调用图:调用 3 个内部函数(collect_mentions, linked_skill_mention, make_skill);外部调用 4 个(new, new, assert_eq!, vec!)。

collect_explicit_skill_mentions_skips_ambiguous_name232–245 ↗
fn collect_explicit_skill_mentions_skips_ambiguous_name()

作用:确认如果两个技能名字相同,只靠 $demo-skill 这种普通名字提法不会选任何一个。因为系统不知道用户到底指哪一个。

数据流:它造两个同名但路径不同的技能;文字两次提到同一个名字;collect_mentions 返回空列表;断言确认系统没有随便挑一个。

调用关系:它用 make_skill 制造“重名”局面,再调用 collect_mentions,保护选择逻辑在信息不够时保持保守。

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

collect_explicit_skill_mentions_prefers_linked_path_over_name248–264 ↗
fn collect_explicit_skill_mentions_prefers_linked_path_over_name()

作用:确认当普通名字有歧义,但链接里给了明确路径时,系统按路径选对应技能。

数据流:它造两个同名技能 alpha 路径和 beta 路径;文字既有 $demo-skill,又有指向 /tmp/beta 的链接;collect_mentions 应只返回 beta。

调用关系:它结合 make_skill、linked_skill_mention 和 collect_mentions,验证“明确路径比模糊名字更可信”的规则。

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

collect_explicit_skill_mentions_skips_plain_name_when_connector_matches267–279 ↗
fn collect_explicit_skill_mentions_skips_plain_name_when_connector_matches()

作用:确认如果某个普通名字也可能是连接器名字,系统不会把 $alpha-skill 当成本地技能。连接器可以理解成外部服务入口,和技能名可能撞名。

数据流:它造一个 alpha 技能,同时设置 connector_counts 里也有 alpha-skill;输入只写普通 $alpha-skill;collect_mentions 返回空;说明名字冲突时不按技能处理。

调用关系:它调用 collect_mentions 检查连接器冲突规则,避免系统在“这是技能还是连接器”不清楚时误选技能。

调用图:调用 2 个内部函数(collect_mentions, make_skill);外部调用 4 个(from, new, assert_eq!, vec!)。

collect_explicit_skill_mentions_allows_explicit_path_with_connector_conflict282–294 ↗
fn collect_explicit_skill_mentions_allows_explicit_path_with_connector_conflict()

作用:确认即使名字和连接器冲突,只要用户给了明确路径,系统仍然可以选中对应技能。

数据流:它造 alpha 技能,并设置同名连接器计数;输入使用带 /tmp/alpha 路径的链接;collect_mentions 返回 alpha;断言确认明确路径能消除歧义。

调用关系:它和上一条测试成对出现:上一条禁止模糊名字,这一条允许明确路径。它用 make_skill 和 collect_mentions 验证路径优先级。

调用图:调用 2 个内部函数(collect_mentions, make_skill);外部调用 4 个(from, new, assert_eq!, vec!)。

collect_explicit_skill_mentions_skips_when_linked_path_disabled297–311 ↗
fn collect_explicit_skill_mentions_skips_when_linked_path_disabled()

作用:确认链接路径如果指向被禁用的技能,系统不会选择它。

数据流:它造两个同名技能,输入链接指向 /tmp/alpha,同时把 /tmp/alpha 放进禁用集合;collect_mentions 返回空列表;断言确认禁用路径被跳过。

调用关系:它使用 make_skill、test_path_buf 和 collect_mentions,测试“明确但被禁用”的情况,确保禁用规则高于路径明确性。

调用图:调用 2 个内部函数(collect_mentions, make_skill);外部调用 5 个(new, from, assert_eq!, test_path_buf, vec!)。

collect_explicit_skill_mentions_prefers_resource_path314–327 ↗
fn collect_explicit_skill_mentions_prefers_resource_path()

作用:确认链接里给出的资源路径会直接决定选哪个技能,即使有同名技能存在。

数据流:它造两个同名技能,链接指向 /tmp/beta;collect_mentions 应返回 beta;断言确认没有被名字相同的 alpha 干扰。

调用关系:它通过 linked_skill_mention 造带路径提法,再调用 collect_mentions,进一步固定“路径是更强证据”的行为。

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

collect_explicit_skill_mentions_skips_missing_path_with_no_fallback330–343 ↗
fn collect_explicit_skill_mentions_skips_missing_path_with_no_fallback()

作用:确认链接路径不存在时,哪怕有多个同名技能,也不会退回按名字匹配。

数据流:它造两个同名技能,但输入链接指向 /tmp/missing;collect_mentions 找不到对应路径后返回空;断言确认没有 fallback。

调用关系:它调用 make_skill 和 collect_mentions,测试缺失路径加重名的危险场景,防止系统猜错目标。

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

collect_explicit_skill_mentions_skips_missing_path_without_fallback346–358 ↗
fn collect_explicit_skill_mentions_skips_missing_path_without_fallback()

作用:确认即使只有一个同名技能,链接路径不存在时也不会退回按名字选它。

数据流:它造一个 demo-skill,输入链接却指向 /tmp/missing;collect_mentions 返回空列表;断言确认路径错误就算失败,不再凭名字补救。

调用关系:它是缺失路径规则的单技能版本。测试运行器执行它,collect_mentions 负责跑真实逻辑,断言保证系统不做过度猜测。

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

core-skills/src/invocation_utils_tests.rs源码 ↗
testtest

可以把这个文件理解成一组“考题”。真正干活的代码在别处,这里负责拿几种常见命令来检查它们是否判断正确。比如,看到 python3 scripts/fetch_comments.py,系统应该知道这是在跑某个技能的脚本;但看到 python3 -c print(1),就不该误以为是在跑脚本。再比如,用户用 catnl 去读 SKILL.md,系统应该认出这是在查看技能文档。文件里还专门测试了绝对路径和相对路径两种情况,避免因为当前目录不同就识别失败。辅助函数会造一个假的技能资料,好让测试不用依赖真实技能目录。整体上,这个文件像质检员,确保“从命令里识别技能使用”的规则稳定可靠。

函数细节8
test_skill_metadata14–26 ↗
fn test_skill_metadata(skill_doc_path: AbsolutePathBuf) -> SkillMetadata

作用:这个函数造出一份假的技能信息,供测试使用。这样每个测试不用重复写同一堆字段,也不用真的准备一个完整技能。

数据流:进去的是一个技能文档路径,也就是假的 SKILL.md 在哪里。函数把这个路径塞进一份 SkillMetadata,再填上固定的名字、描述、范围等测试用信息。出来的是一份可以被识别逻辑使用的假技能资料,不会改动外部状态。

调用关系:它是多个测试的准备工具。读文档识别测试和跑脚本识别测试都会先调用它,造出名叫 test-skill 的技能,然后把这份技能放进 SkillLoadOutcome,让被测函数有东西可以匹配。

调用图:被 4 处调用(skill_doc_read_detection_matches_absolute_path, skill_doc_read_detection_matches_shared_read_parser, skill_script_run_detection_matches_absolute_path_from_any_workdir, skill_script_run_detection_matches_relative_path_from_skill_root)。

test_path_display28–30 ↗
fn test_path_display(unix_path: &str) -> String

作用:这个函数把测试用的 Unix 风格路径转成当前测试环境里显示出来的路径字符串。它让测试在不同操作系统上更稳,尤其是路径分隔符可能不一样时。

数据流:进去的是一个像 /tmp/skill-test/SKILL.md 这样的路径文本。函数先用 test_path_buf 把它变成测试用路径对象,再调用显示格式,把它转回字符串。出来的是适合放进命令参数里的路径字符串。

调用关系:它服务于需要把路径放进命令 tokens 的测试。比如测试 cat 某个文件python3 某个脚本 时,测试会用它生成参数文本;它内部把路径转换工作交给 test_path_buf

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

script_run_detection_matches_runner_plus_extension33–41 ↗
fn script_run_detection_matches_runner_plus_extension()

作用:这个测试确认:当命令看起来像“用脚本运行器运行一个脚本文件”时,系统能识别出来。例子是 python3 -u scripts/fetch_comments.py

数据流:进去的是测试自己构造的一组命令片段:运行器 python3、参数 -u、脚本路径 scripts/fetch_comments.py。测试把这些交给 script_run_token,看它能不能找到脚本运行的关键片段。出来的结果应该是“找到了”,测试用断言确认这一点。

调用关系:这是对 script_run_token 的直接检查。它不搭建完整技能资料,只关心最基础的一步:命令里是否存在像脚本执行的东西;最后通过 assert_eq! 判断结果是否符合预期。

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

script_run_detection_excludes_python_c44–52 ↗
fn script_run_detection_excludes_python_c()

作用:这个测试确认:python3 -c print(1) 这种直接执行一小段代码的命令,不应该被误判成运行脚本文件。

数据流:进去的是一组命令片段:python3-cprint(1)。测试把它交给 script_run_token,让它判断有没有脚本文件被运行。出来的结果应该是“没有找到”,因为这里没有 .py 脚本路径,只有一段内联代码。

调用关系:它也是直接测试 script_run_token,但检查的是反例。前一个测试证明该识别的要识别,这个测试证明不该识别的不能乱识别。

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

skill_doc_read_detection_matches_absolute_path55–77 ↗
fn skill_doc_read_detection_matches_absolute_path()

作用:这个测试确认:如果用户用绝对路径读取某个 SKILL.md,系统能认出这是在读对应技能的说明文档。

数据流:测试先造出 /tmp/skill-test/SKILL.md 这个技能文档路径,并把它规范化,也就是转成统一可比较的路径形式。然后用 test_skill_metadata 造一份假技能,把它按文档路径放进 SkillLoadOutcome。接着构造命令片段,像 cat /tmp/skill-test/SKILL.md | head。这些信息交给 detect_skill_doc_read 后,预期出来的是这个假技能,名字是 test-skill

调用关系:它测试的是完整的“读技能文档识别”流程。准备阶段会用 test_path_bufcanonicalize_if_existstest_skill_metadata 搭好假环境;真正判断交给 detect_skill_doc_read;最后用 assert_eq! 检查识别到的技能名。

调用图:调用 1 个内部函数(test_skill_metadata);外部调用 9 个(new, default, from, new, assert_eq!, test_path_buf, canonicalize_if_exists, detect_skill_doc_read, vec!)。

skill_doc_read_detection_matches_shared_read_parser80–101 ↗
fn skill_doc_read_detection_matches_shared_read_parser()

作用:这个测试确认:系统不只认识 cat,也能通过共享的读文件解析规则,识别像 nl -ba SKILL.md 这样的读文档命令。

数据流:测试先准备同一个假的技能文档路径,并把这份技能按规范化后的文档路径登记好。然后构造命令片段:nl -ba /tmp/skill-test/SKILL.md,其中 nl 是给文件加行号显示的工具。函数把命令和当前目录交给 detect_skill_doc_read,预期输出是对应的 test-skill

调用关系:它和绝对路径读取测试很像,但换了一个读文件工具,重点是覆盖“共享读文件解析器”的行为。它同样依赖 test_skill_metadata 准备技能资料,最后由 detect_skill_doc_read 做判断。

调用图:调用 1 个内部函数(test_skill_metadata);外部调用 9 个(new, default, from, new, assert_eq!, test_path_buf, canonicalize_if_exists, detect_skill_doc_read, vec!)。

skill_script_run_detection_matches_relative_path_from_skill_root104–124 ↗
fn skill_script_run_detection_matches_relative_path_from_skill_root()

作用:这个测试确认:当用户站在技能根目录里,用相对路径运行 scripts 目录下的脚本时,系统能认出这是在运行该技能的脚本。

数据流:测试先准备技能文档路径和脚本目录 /tmp/skill-test/scripts,并把脚本目录规范化后登记到 SkillLoadOutcome。然后构造命令 python3 scripts/fetch_comments.py,当前工作目录设为 /tmp/skill-test。这些输入交给 detect_skill_script_run 后,预期输出是名叫 test-skill 的技能。

调用关系:它测试的是“从相对脚本路径识别技能”的流程。准备技能资料时调用 test_skill_metadata,准备路径时调用 test_path_bufcanonicalize_if_exists,真正匹配交给 detect_skill_script_run

调用图:调用 1 个内部函数(test_skill_metadata);外部调用 9 个(new, default, from, new, assert_eq!, test_path_buf, canonicalize_if_exists, detect_skill_script_run, vec!)。

skill_script_run_detection_matches_absolute_path_from_any_workdir127–147 ↗
fn skill_script_run_detection_matches_absolute_path_from_any_workdir()

作用:这个测试确认:即使当前目录不在技能目录里,只要命令里用了脚本的绝对路径,系统也能识别出对应技能。

数据流:测试准备同一个技能脚本目录,并把它登记为某个技能的脚本目录。然后构造命令 python3 /tmp/skill-test/scripts/fetch_comments.py,但当前工作目录故意设成 /tmp/other。这些交给 detect_skill_script_run 后,结果应该仍然是 test-skill,说明识别不依赖当前目录刚好正确。

调用关系:它补上了绝对路径场景,和相对路径测试一起保证脚本识别更可靠。它用 test_skill_metadata 和路径工具搭好假数据,再把判断交给 detect_skill_script_run,最后用断言确认匹配结果。

调用图:调用 1 个内部函数(test_skill_metadata);外部调用 9 个(new, default, from, new, assert_eq!, test_path_buf, canonicalize_if_exists, detect_skill_script_run, vec!)。

core-skills/src/loader_tests.rs源码 ↗
testtest

可以把这个文件看成技能加载器的“体检清单”。测试会临时造出假的用户目录、系统目录、项目目录和插件目录,再往里面写入各种 Skills.md 文件和额外元数据,然后调用真正的加载函数,看结果是不是符合预期。它覆盖了很多容易踩坑的情况:技能来自用户、仓库、系统还是管理员目录;隐藏目录和坏格式要不要跳过;同名技能怎么保留;同一路径怎么去重;符号链接(像快捷方式一样指向别处的文件或目录)能不能安全处理;图标路径是否只能待在允许的 assets 目录里;插件技能名是否要加插件前缀;描述、短描述、默认提示词有没有长度限制。这个文件本身不提供线上功能,但它保证加载器以后改代码时不会破坏这些规则。

函数细节57
make_config29–31 ↗
async fn make_config(codex_home: &TempDir) -> TestConfig

作用:给测试快速做一份默认配置。测试不想每次都手写系统、用户、项目这些配置层,所以用它一键准备。

数据流:输入一个临时的 Codex 主目录 → 把当前工作目录默认设成这个临时目录 → 调用 make_config_for_cwd 生成 TestConfig,里面有绝对路径形式的 cwd 和配置层栈。

调用关系:很多测试先用它搭好干净环境,再交给 load_skills_for_test 去真正加载技能;它把细节都转交给 make_config_for_cwd。

调用图:调用 1 个内部函数(make_config_for_cwd);被 19 处调用(accepts_icon_paths_under_assets_dir, does_not_loop_on_symlink_cycle_for_user_scope, drops_interface_when_icons_are_invalid, empty_skill_policy_defaults_to_allow_implicit_invocation, enforces_length_limits, enforces_short_description_length_limits, falls_back_to_directory_name_when_skill_name_is_missing, ignores_default_prompt_over_max_length, ignores_invalid_brand_color, ignores_symlinked_skill_file_for_user_scope (+9 more));外部调用 1 个(path)。

config_file33–35 ↗
fn config_file(path: PathBuf) -> AbsolutePathBuf

作用:把普通路径转成测试里需要的绝对路径。这样后面的配置层不会因为相对路径而受运行位置影响。

数据流:输入一个 PathBuf 路径 → 调用 abs 把它变成 AbsolutePathBuf → 返回这个绝对路径。

调用关系:它是 make_config_for_cwd 里构造系统配置文件和用户配置文件位置的小帮手。

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

project_layers_for_cwd37–80 ↗
fn project_layers_for_cwd(cwd: &Path) -> Vec<ConfigLayerEntry>

作用:根据当前目录,模拟项目里的 .codex 配置层。它用来测试仓库技能搜索时,哪些项目目录应该参与。

数据流:输入一个当前路径,可能是目录也可能是文件 → 如果是文件就取它的父目录,再往上找 .git 作为项目根;从项目根到当前目录之间收集存在 .codex 的目录 → 输出这些项目配置层。

调用关系:make_config_for_cwd 会调用它,把项目配置层插到系统和用户配置层后面,之后技能根目录推导逻辑会使用这些层。

调用图:被 1 处调用(make_config_for_cwd);外部调用 3 个(is_dir, parent, to_path_buf)。

make_config_for_cwd82–119 ↗
async fn make_config_for_cwd(codex_home: &TempDir, cwd: PathBuf) -> TestConfig

作用:给指定当前目录做一份完整测试配置。它让测试能模拟“我现在在某个仓库、子目录或文件里运行”的情况。

数据流:输入临时 Codex 主目录和 cwd → 创建假的系统配置目录,准备系统层、用户层,再加上 project_layers_for_cwd 找到的项目层 → 输出 TestConfig。

调用关系:大量仓库相关测试直接调用它;make_config 也只是它的简化版。生成的配置随后通常会传给 load_skills_for_test。

调用图:调用 2 个内部函数(new, project_layers_for_cwd);被 12 处调用(keeps_duplicate_names_from_nested_codex_dirs, keeps_duplicate_names_from_repo_and_user, loads_skills_from_agents_dir_without_codex_dir, loads_skills_from_all_codex_dirs_under_project_root, loads_skills_from_codex_dir_when_not_git_repo, loads_skills_from_repo_root, loads_skills_from_system_cache_when_present, loads_skills_via_symlinked_subdir_for_repo_scope, loads_skills_when_cwd_is_file_in_repo, make_config (+2 more));外部调用 6 个(abs, path, default, default, create_dir_all, vec!)。

load_skills_for_test121–133 ↗
async fn load_skills_for_test(config: &TestConfig) -> SkillLoadOutcome

作用:用测试配置实际跑一次技能加载。它故意不扫描真实用户家目录,避免测试被机器上的真实文件污染。

数据流:输入 TestConfig → 先根据配置层算出要扫描的技能根目录,再调用 load_skills_from_roots 读取技能 → 输出 SkillLoadOutcome,里面有成功技能和错误列表。

调用关系:多数测试在写好临时技能文件后调用它;它把测试环境和真正的加载器连接起来。

调用图:被 29 处调用(accepts_icon_paths_under_assets_dir, does_not_loop_on_symlink_cycle_for_user_scope, drops_interface_when_icons_are_invalid, empty_skill_policy_defaults_to_allow_implicit_invocation, enforces_length_limits, enforces_short_description_length_limits, falls_back_to_directory_name_when_skill_name_is_missing, ignores_default_prompt_over_max_length, ignores_invalid_brand_color, ignores_symlinked_skill_file_for_user_scope (+15 more));外部调用 3 个(clone, load_skills_from_roots, skill_roots_from_layer_stack)。

mark_as_git_repo135–139 ↗
fn mark_as_git_repo(dir: &Path)

作用:把一个临时目录伪装成 Git 仓库。测试只需要有 .git 标记,不需要真的运行 git init。

数据流:输入目录路径 → 在里面写一个假的 .git 文件 → 后续项目根发现逻辑就会把这里当作仓库根。

调用关系:仓库技能相关测试先调用它,再用 make_config_for_cwd 和 load_skills_for_test 验证仓库范围规则。

调用图:被 8 处调用(keeps_duplicate_names_from_nested_codex_dirs, keeps_duplicate_names_from_repo_and_user, loads_skills_from_agents_dir_without_codex_dir, loads_skills_from_all_codex_dirs_under_project_root, loads_skills_from_repo_root, loads_skills_via_symlinked_subdir_for_repo_scope, loads_skills_when_cwd_is_file_in_repo, repo_skills_search_does_not_escape_repo_root);外部调用 2 个(join, write)。

normalized141–145 ↗
fn normalized(path: &Path) -> AbsolutePathBuf

作用:把路径整理成稳定的绝对路径,方便测试比较。这样符号链接、相对路径等差异不会让断言误判。

数据流:输入路径 → 尝试规范化;如果规范化失败就退回原路径 → 转成绝对路径并返回。

调用关系:很多断言用它生成期望的 path_to_skills_md 或图标路径,确保和加载器输出的路径格式一致。

调用图:被 5 处调用(accepts_icon_paths_under_assets_dir, ignores_default_prompt_over_max_length, keeps_duplicate_names_from_nested_codex_dirs, loads_plugin_skill_interface_icons_from_shared_plugin_assets, loads_skill_interface_metadata_from_yaml);外部调用 1 个(canonicalize)。

skill_roots_from_layer_stack_maps_user_to_user_and_system_cache_and_system_to_admin148–210 ↗
async fn skill_roots_from_layer_stack_maps_user_to_user_and_system_cache_and_system_to_admin() -> anyhow::Result<()>

作用:检查配置层会被翻译成正确的技能搜索目录。重点是用户配置、用户家目录里的 .agents、系统缓存和管理员目录的对应关系。

数据流:临时创建系统目录和用户目录 → 构造系统层、用户层配置栈 → 调用 skill_roots_from_layer_stack → 断言返回的作用域和路径顺序正确。

调用关系:这是对 skill_roots_from_layer_stack 的直接测试,不经过完整技能读取,专门守住“去哪找技能”这一步。

调用图:调用 1 个内部函数(new);外部调用 7 个(clone, default, assert_eq!, default, create_dir_all, tempdir, vec!)。

skill_roots_from_layer_stack_includes_disabled_project_layers213–279 ↗
async fn skill_roots_from_layer_stack_includes_disabled_project_layers() -> anyhow::Result<()>

作用:确认即使项目配置层被标成禁用,项目技能目录仍会被列入搜索。这样不可信配置不会阻止仓库技能目录被识别。

数据流:创建用户目录和项目 .codex 目录 → 构造一个禁用的 Project 配置层 → 计算技能根目录 → 断言 Repo、User、System 等路径都按预期出现。

调用关系:它直接验证 skill_roots_from_layer_stack 的项目层处理规则。

调用图:调用 1 个内部函数(new);外部调用 7 个(clone, default, assert_eq!, default, create_dir_all, tempdir, vec!)。

loads_skills_from_home_agents_dir_for_user_scope282–340 ↗
async fn loads_skills_from_home_agents_dir_for_user_scope() -> anyhow::Result<()>

作用:确认用户家目录下 .agents/skills 里的技能会作为用户技能加载。这个目录是兼容另一套放技能位置的入口。

数据流:创建用户配置层,在 home/.agents/skills 写一个技能 → 算出 roots 并加载 → 检查结果里有这个技能,作用域是 User,且没有错误。

调用关系:它用 write_skill_at 造文件,然后直接调用 skill_roots_from_layer_stack 和 load_skills_from_roots,验证完整路径到加载结果。

调用图:调用 2 个内部函数(new, write_skill_at);外部调用 8 个(clone, default, assert!, assert_eq!, default, create_dir_all, tempdir, vec!)。

write_skill342–344 ↗
fn write_skill(codex_home: &TempDir, dir: &str, name: &str, description: &str) -> PathBuf

作用:在测试的默认用户技能目录里写一个标准技能文件。它让测试少写重复的文件创建代码。

数据流:输入临时 Codex 主目录、子目录名、技能名和描述 → 转到 codex_home/skills 下 → 调用 write_skill_at 写出 Skills.md → 返回文件路径。

调用关系:许多测试用它准备普通用户技能;真正写文件的工作交给 write_skill_at。

调用图:调用 1 个内部函数(write_skill_at);被 13 处调用(accepts_icon_paths_under_assets_dir, drops_interface_when_icons_are_invalid, empty_skill_policy_defaults_to_allow_implicit_invocation, enforces_length_limits, ignores_default_prompt_over_max_length, ignores_invalid_brand_color, keeps_duplicate_names_from_repo_and_user, loads_skill_dependencies_metadata_from_yaml, loads_skill_interface_metadata_from_yaml, loads_skill_policy_from_yaml (+3 more));外部调用 1 个(path)。

write_system_skill346–353 ↗
fn write_system_skill(codex_home: &TempDir, dir: &str, name: &str, description: &str) -> PathBuf

作用:在测试的系统缓存技能目录里写一个技能。用来验证系统作用域的技能是否会被加载。

数据流:输入临时 Codex 主目录、子目录名、技能名和描述 → 指向 codex_home/skills/.system → 调用 write_skill_at → 返回写好的文件路径。

调用关系:loads_skills_from_system_cache_when_present 用它造系统技能。

调用图:调用 1 个内部函数(write_skill_at);被 1 处调用(loads_skills_from_system_cache_when_present);外部调用 1 个(path)。

write_skill_at355–364 ↗
fn write_skill_at(root: &Path, dir: &str, name: &str, description: &str) -> PathBuf

作用:在指定根目录下写一个带标准 YAML 头信息的 Skills.md。YAML 头信息就是文件开头用来写名称、描述等机器可读信息的一段文本。

数据流:输入根目录、技能目录名、名称、描述 → 创建目录,把多行描述缩进成合法格式 → 写入 Skills.md → 返回文件路径。

调用关系:这是本文件最常用的造数工具;很多测试、write_skill 和 write_system_skill 都靠它生成合法技能。

调用图:被 21 处调用(deduplicates_by_path_preferring_first_root, does_not_loop_on_symlink_cycle_for_user_scope, drops_plugin_skill_interface_icons_that_escape_shared_plugin_assets, ignores_symlinked_skill_file_for_user_scope, keeps_duplicate_names_from_nested_codex_dirs, keeps_duplicate_names_from_repo_and_user, loads_plugin_skill_interface_icons_from_shared_plugin_assets, loads_skills_from_agents_dir_without_codex_dir, loads_skills_from_all_codex_dirs_under_project_root, loads_skills_from_codex_dir_when_not_git_repo (+11 more));外部调用 4 个(join, format!, create_dir_all, write)。

write_raw_skill_at366–373 ↗
fn write_raw_skill_at(root: &Path, dir: &str, frontmatter: &str) -> PathBuf

作用:写一个自定义头信息的技能文件。它适合测试缺字段、超长字段、插件命名等特殊情况。

数据流:输入根目录、目录名和原始 frontmatter 文本 → 创建目录,把这些文本包在 --- 分隔线中 → 写入 Skills.md → 返回路径。

调用关系:需要精确控制技能头信息的测试会用它,比如缺少 name、插件名称限制等。

调用图:被 4 处调用(falls_back_to_directory_name_when_skill_name_is_missing, namespaces_plugin_skills_using_plugin_name, plugin_skill_name_length_limit_allows_max_qualified_name, plugin_skill_name_length_limit_rejects_overlong_qualified_name);外部调用 4 个(join, format!, create_dir_all, write)。

write_skill_metadata_at375–384 ↗
fn write_skill_metadata_at(skill_dir: &Path, contents: &str) -> PathBuf

作用:给某个技能目录写额外的元数据文件。元数据文件用来描述依赖、界面展示、策略等 Skills.md 之外的信息。

数据流:输入技能目录和内容字符串 → 创建 .codex/skill.json 所在目录 → 写入内容 → 返回元数据文件路径。

调用关系:依赖、策略、界面相关测试直接用它;write_skill_interface_at 也复用它。

调用图:被 5 处调用(empty_skill_policy_defaults_to_allow_implicit_invocation, loads_skill_dependencies_metadata_from_yaml, loads_skill_policy_from_yaml, loads_skill_policy_products_from_yaml, write_skill_interface_at);外部调用 3 个(join, create_dir_all, write)。

write_skill_interface_at386–388 ↗
fn write_skill_interface_at(skill_dir: &Path, contents: &str) -> PathBuf

作用:给技能写界面展示相关的元数据,比如显示名、图标、品牌色。它只是给语义更清楚的测试起了一个别名。

数据流:输入技能目录和界面元数据内容 → 转交给 write_skill_metadata_at 写文件 → 返回写好的路径。

调用关系:界面和图标相关测试调用它,实际文件写入由 write_skill_metadata_at 完成。

调用图:调用 1 个内部函数(write_skill_metadata_at);被 7 处调用(accepts_icon_paths_under_assets_dir, drops_interface_when_icons_are_invalid, drops_plugin_skill_interface_icons_that_escape_shared_plugin_assets, ignores_default_prompt_over_max_length, ignores_invalid_brand_color, loads_plugin_skill_interface_icons_from_shared_plugin_assets, loads_skill_interface_metadata_from_yaml)。

loads_skill_dependencies_metadata_from_yaml391–476 ↗
async fn loads_skill_dependencies_metadata_from_yaml()

作用:检查技能额外元数据里的工具依赖能被读出来。这里的依赖包括 MCP 服务和命令行工具,MCP 可以理解为给模型接入外部工具的一种协议。

数据流:写一个普通技能,再写 dependencies 元数据 → 生成测试配置并加载 → 断言输出里包含三个工具依赖及其 transport、command、url 等字段。

调用关系:它用 write_skill、write_skill_metadata_at 造数据,再通过 make_config 和 load_skills_for_test 验证加载器解析依赖的能力。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, write_skill, write_skill_metadata_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_skill_interface_metadata_from_yaml479–532 ↗
async fn loads_skill_interface_metadata_from_yaml()

作用:检查界面元数据能被正确读取和清洗。比如多余空格会被压缩,图标路径会变成绝对路径。

数据流:写技能和 interface 元数据 → 加载技能 → 只取用户技能 → 断言 display_name、short_description、图标、品牌色、默认提示词都符合预期。

调用关系:它依赖 write_skill_interface_at 准备元数据,用 normalized 生成期望路径,再用 load_skills_for_test 验证结果。

调用图:调用 5 个内部函数(load_skills_for_test, make_config, normalized, write_skill, write_skill_interface_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_skill_policy_from_yaml535–565 ↗
async fn loads_skill_policy_from_yaml()

作用:检查策略里的 allow_implicit_invocation: false 会生效。这个开关表示技能不能被系统自动悄悄调用。

数据流:写技能和 policy 元数据 → 加载 → 检查技能 policy 字段为 false,并且允许自动调用的技能列表为空。

调用关系:它通过 load_skills_for_test 观察加载结果,也顺带测试 SkillLoadOutcome 的 allowed_skills_for_implicit_invocation 行为。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, write_skill, write_skill_metadata_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

empty_skill_policy_defaults_to_allow_implicit_invocation568–600 ↗
async fn empty_skill_policy_defaults_to_allow_implicit_invocation()

作用:检查空策略不会禁止自动调用。也就是说,只写 policy: {} 时仍然按默认允许处理。

数据流:写一个 policy 为空对象的技能 → 加载 → 断言 allow_implicit_invocation 是 None,并且允许自动调用列表等于所有技能。

调用关系:它和 loads_skill_policy_from_yaml 形成对照,验证显式 false 和空策略的区别。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, write_skill, write_skill_metadata_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_skill_policy_products_from_yaml603–635 ↗
async fn loads_skill_policy_products_from_yaml()

作用:检查策略里声明适用产品的列表能被识别。产品名大小写不同也应该能解析。

数据流:写 policy.products,包含 codex、CHATGPT、atlas → 加载 → 断言它们被转成 Product::Codex、Product::Chatgpt、Product::Atlas。

调用关系:它复用元数据写入和加载流程,专门覆盖产品枚举解析。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, write_skill, write_skill_metadata_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

accepts_icon_paths_under_assets_dir638–686 ↗
async fn accepts_icon_paths_under_assets_dir()

作用:确认技能界面图标只要放在 assets 目录下就会被接受。这样图标可以安全地随技能一起打包。

数据流:写技能和包含 icon_small、icon_large 的元数据 → 加载 → 断言图标路径被解析到技能目录下的 assets 文件。

调用关系:它调用 write_skill_interface_at 准备图标字段,用 normalized 计算期望绝对路径。

调用图:调用 5 个内部函数(load_skills_for_test, make_config, normalized, write_skill, write_skill_interface_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

ignores_invalid_brand_color689–727 ↗
async fn ignores_invalid_brand_color()

作用:检查非法品牌色会被忽略。比如写 blue 不符合十六进制颜色格式,就不应该进入结果。

数据流:写一个 brand_color 为 blue 的界面元数据 → 加载 → 断言整个 interface 被丢弃,技能本身仍然加载成功。

调用关系:它验证界面元数据校验逻辑:坏展示信息不应让技能文件本身失败。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, write_skill, write_skill_interface_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

ignores_default_prompt_over_max_length730–781 ↗
async fn ignores_default_prompt_over_max_length()

作用:检查默认提示词太长时会被丢掉。这样可以防止一个技能塞入过大的默认文本。

数据流:生成超过 MAX_DEFAULT_PROMPT_LEN 的字符串,写进 interface.default_prompt → 加载 → 断言其他合法界面字段保留,default_prompt 为空。

调用关系:它用格式化文本构造超长元数据,随后通过 load_skills_for_test 验证长度限制。

调用图:调用 5 个内部函数(load_skills_for_test, make_config, normalized, write_skill, write_skill_interface_at);外部调用 4 个(assert!, assert_eq!, format!, tempdir)。

drops_interface_when_icons_are_invalid784–823 ↗
async fn drops_interface_when_icons_are_invalid()

作用:检查图标路径不安全或不合规时,界面信息会被整体丢弃。比如不在 assets 下,或试图用 .. 跳出目录。

数据流:写包含 icon.png 和 ./assets/../logo.svg 的元数据 → 加载 → 断言 interface 为 None,但技能仍存在。

调用关系:它和 accepts_icon_paths_under_assets_dir 互为正反测试,守住图标路径安全规则。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, write_skill, write_skill_interface_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_plugin_skill_interface_icons_from_shared_plugin_assets826–884 ↗
async fn loads_plugin_skill_interface_icons_from_shared_plugin_assets()

作用:检查插件技能可以引用插件根目录共享 assets 里的图标。插件的多个技能可以共用一套素材。

数据流:创建插件目录、技能目录和插件级 assets/logo.svg → 元数据里用 ../../assets/logo.svg 引用 → 加载插件技能根 → 断言图标路径指向插件 assets,且 plugin_id 被保留。

调用关系:它直接构造 SkillRoot 调用 load_skills_from_roots,不走 make_config,专门测试插件根目录规则。

调用图:调用 3 个内部函数(normalized, write_skill_at, write_skill_interface_at);外部调用 6 个(clone, assert!, assert_eq!, create_dir_all, write, tempdir)。

drops_plugin_skill_interface_icons_that_escape_shared_plugin_assets887–933 ↗
async fn drops_plugin_skill_interface_icons_that_escape_shared_plugin_assets()

作用:检查插件图标不能逃出共享 assets 目录。这样插件不能借图标字段引用任意外部文件。

数据流:创建插件技能,图标写成 ../../other/logo.svg → 加载 → 断言 interface 被丢弃,技能仍被加载且带 plugin_id。

调用关系:它是插件图标安全规则的反面测试,直接调用 load_skills_from_roots。

调用图:调用 2 个内部函数(write_skill_at, write_skill_interface_at);外部调用 4 个(clone, assert!, assert_eq!, tempdir)。

loads_skills_via_symlinked_subdir_for_user_scope947–978 ↗
async fn loads_skills_via_symlinked_subdir_for_user_scope()

作用:确认用户技能目录下的符号链接子目录可以被扫描。也就是把一个共享技能目录链接进来时,用户技能能加载。

数据流:在共享目录写技能 → 在 codex_home/skills 下创建指向共享目录的链接 → 加载 → 断言技能路径是共享目录里的真实文件,作用域是 User。

调用关系:它使用 symlink_dir 和 write_skill_at 构造场景,再通过 make_config、load_skills_for_test 验证用户作用域行为。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, symlink_dir, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

ignores_symlinked_skill_file_for_user_scope982–1001 ↗
async fn ignores_symlinked_skill_file_for_user_scope()

作用:确认用户作用域不会加载被符号链接过来的单个 Skills.md 文件。允许目录链接,但不允许直接链接技能文件。

数据流:在共享目录写真实技能文件 → 在用户技能目录中创建指向该文件的链接 → 加载 → 断言没有技能被加载。

调用关系:它用 symlink_file 构造文件级链接,检验加载器对文件链接的限制。

调用图:调用 4 个内部函数(load_skills_for_test, make_config, symlink_file, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

loads_skills_via_symlinked_subdir_for_admin_scope1042–1079 ↗
async fn loads_skills_via_symlinked_subdir_for_admin_scope()

作用:确认管理员作用域也允许通过符号链接子目录加载技能。管理员目录可以引用共享技能位置。

数据流:在共享目录写技能 → 把共享目录链接到管理员根目录下 → 直接加载该根目录 → 断言技能作用域是 Admin。

调用关系:它直接调用 load_skills_from_roots,专门测试 Admin 作用域的链接目录行为。

调用图:调用 2 个内部函数(symlink_dir, write_skill_at);外部调用 5 个(clone, assert!, assert_eq!, create_dir_all, tempdir)。

loads_skills_via_symlinked_subdir_for_repo_scope1083–1119 ↗
async fn loads_skills_via_symlinked_subdir_for_repo_scope()

作用:确认仓库作用域允许通过符号链接子目录加载技能。这样仓库 .codex/skills 可以链接到共享目录。

数据流:把临时目录标成 Git 仓库,在仓库技能根下放目录链接,链接目标里有技能 → 生成 cwd 配置并加载 → 断言技能作用域是 Repo。

调用关系:它结合 mark_as_git_repo、symlink_dir、make_config_for_cwd 和 load_skills_for_test,覆盖仓库链接场景。

调用图:调用 5 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, symlink_dir, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

system_scope_ignores_symlinked_subdir1123–1147 ↗
async fn system_scope_ignores_symlinked_subdir()

作用:确认系统作用域不跟随符号链接子目录。系统缓存更敏感,所以不允许通过链接引入外部内容。

数据流:在共享目录写技能,把它链接到 system 根下 → 以 System 作用域加载 → 断言没有技能被加载。

调用关系:它和用户、仓库、管理员的链接测试形成对比,说明不同作用域的安全策略不同。

调用图:调用 2 个内部函数(symlink_dir, write_skill_at);外部调用 5 个(clone, assert!, assert_eq!, create_dir_all, tempdir)。

respects_max_scan_depth_for_user_scope1150–1195 ↗
async fn respects_max_scan_depth_for_user_scope()

作用:检查扫描目录有最大深度限制。这样很深的目录树不会拖慢或拖垮加载过程。

数据流:在允许深度内写一个技能,在更深一层再写一个技能 → 加载用户根目录 → 断言只加载浅一点的那个。

调用关系:它直接构造 SkillRoot 调用 load_skills_from_roots,专门验证扫描深度规则。

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

loads_valid_skill1198–1223 ↗
async fn loads_valid_skill()

作用:检查一个最普通、格式正确的技能能被成功加载。这是最基础的正向测试。

数据流:写一个 name 和多行 description 都合法的技能 → 加载 → 断言描述被整理成单行,技能作用域是 User,且没有错误。

调用关系:它使用 write_skill、make_config 和 load_skills_for_test,证明基本流程可用。

调用图:调用 3 个内部函数(load_skills_for_test, make_config, write_skill);外部调用 3 个(assert!, assert_eq!, tempdir)。

falls_back_to_directory_name_when_skill_name_is_missing1226–1256 ↗
async fn falls_back_to_directory_name_when_skill_name_is_missing()

作用:检查技能文件没有写 name 时,会用目录名当技能名。这样简单技能可以少写一个字段。

数据流:写一个只有 description 的原始技能文件 → 加载 → 断言 name 变成目录名 directory-derived。

调用关系:它用 write_raw_skill_at 精确省略 name,验证加载器的默认命名规则。

调用图:调用 3 个内部函数(load_skills_for_test, make_config, write_raw_skill_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

namespaces_plugin_skills_using_plugin_name1259–1302 ↗
async fn namespaces_plugin_skills_using_plugin_name()

作用:检查插件技能名会加上插件名前缀。这样不同插件里的同名技能不容易撞名。

数据流:创建插件描述文件 plugin.json,写插件技能但不写 name → 加载插件根 → 断言技能名变成 sample:sample-search,并带 plugin_id。

调用关系:它直接调用 load_skills_from_roots,验证插件根和插件清单对技能命名的影响。

调用图:调用 1 个内部函数(write_raw_skill_at);外部调用 6 个(clone, assert!, assert_eq!, create_dir_all, write, tempdir)。

plugin_skill_name_length_limit_allows_max_qualified_name1305–1347 ↗
async fn plugin_skill_name_length_limit_allows_max_qualified_name()

作用:检查插件前缀加技能名刚好达到允许长度时仍然通过。qualified name 指带插件名前缀的完整技能名。

数据流:构造接近上限的插件名和技能名 → 写插件清单和技能 → 加载 → 断言完整名称 plugin:skill 被接受。

调用关系:它和后面的超长拒绝测试配对,守住边界值。

调用图:调用 1 个内部函数(write_raw_skill_at);外部调用 7 个(clone, assert!, assert_eq!, format!, create_dir_all, write, tempdir)。

plugin_skill_name_length_limit_rejects_overlong_qualified_name1350–1380 ↗
async fn plugin_skill_name_length_limit_rejects_overlong_qualified_name()

作用:检查插件完整技能名超过长度限制时会被拒绝。这样列表和调用接口不会收到过长名字。

数据流:构造过长的插件名加技能名 → 写插件清单和技能 → 加载 → 断言没有技能,且错误信息提到 invalid qualified name。

调用关系:它直接测试 load_skills_from_roots 的插件命名校验失败路径。

调用图:调用 1 个内部函数(write_raw_skill_at);外部调用 7 个(clone, assert!, assert_eq!, format!, create_dir_all, write, tempdir)。

loads_short_description_from_metadata1383–1412 ↗
async fn loads_short_description_from_metadata()

作用:检查 Skills.md 头信息里的 metadata.short-description 能被读取。短描述通常用于列表或卡片展示。

数据流:手写一个带 long description 和 short-description 的技能文件 → 加载 → 断言 short_description 字段是 short summary。

调用关系:它不用辅助写技能函数,而是直接写文件,以便精确控制嵌套 metadata。

调用图:调用 2 个内部函数(load_skills_for_test, make_config);外部调用 5 个(assert!, assert_eq!, create_dir_all, write, tempdir)。

enforces_short_description_length_limits1415–1436 ↗
async fn enforces_short_description_length_limits()

作用:检查短描述太长时技能会被拒绝。这样展示用文本不会无限膨胀。

数据流:生成超过 MAX_SHORT_DESCRIPTION_LEN 的短描述 → 写进技能文件 → 加载 → 断言技能数为 0,错误信息指向 metadata.short-description。

调用关系:它通过 make_config 和 load_skills_for_test 走完整加载流程,覆盖校验失败。

调用图:调用 2 个内部函数(load_skills_for_test, make_config);外部调用 6 个(assert!, assert_eq!, format!, create_dir_all, write, tempdir)。

skips_hidden_and_invalid1439–1464 ↗
async fn skips_hidden_and_invalid()

作用:检查隐藏目录会被跳过,而坏格式技能会报告错误。隐藏目录就是名字以点开头的目录。

数据流:在 .hidden 下写合法技能,在 invalid 下写缺少结束 frontmatter 的坏文件 → 加载 → 断言没有技能,只记录一个坏格式错误。

调用关系:它同时验证目录过滤和 YAML 头信息错误处理。

调用图:调用 2 个内部函数(load_skills_for_test, make_config);外部调用 5 个(assert!, assert_eq!, create_dir_all, write, tempdir)。

enforces_length_limits1467–1490 ↗
async fn enforces_length_limits()

作用:检查技能描述的长度限制。刚好到上限可以,超过上限会报错。

数据流:先写一个最大允许长度的描述并加载成功 → 再写一个超长描述 → 重新加载 → 断言只保留合法技能,并出现 description 错误。

调用关系:它用 write_skill 生成两个技能,测试加载器的字段长度边界。

调用图:调用 3 个内部函数(load_skills_for_test, make_config, write_skill);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_skills_from_repo_root1493–1525 ↗
async fn loads_skills_from_repo_root()

作用:检查 Git 仓库根目录的 .codex/skills 会被当作仓库技能来源。这样项目可以自带技能。

数据流:创建假 Git 仓库,在仓库根的 .codex/skills 写技能 → 以仓库目录作为 cwd 加载 → 断言技能作用域是 Repo。

调用关系:它用 mark_as_git_repo 和 make_config_for_cwd 模拟在仓库里运行。

调用图:调用 4 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, write_skill_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_skills_from_agents_dir_without_codex_dir1528–1561 ↗
async fn loads_skills_from_agents_dir_without_codex_dir()

作用:检查仓库里即使没有 .codex,也能从 .agents/skills 加载仓库技能。这是另一种项目技能目录约定。

数据流:创建假 Git 仓库,在 .agents/skills 写技能 → 加载 → 断言该技能以 Repo 作用域出现。

调用关系:它验证仓库技能来源不只限于 .codex/skills。

调用图:调用 4 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, write_skill_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

loads_skills_from_all_codex_dirs_under_project_root1564–1627 ↗
async fn loads_skills_from_all_codex_dirs_under_project_root()

作用:检查从仓库根到当前子目录之间的多个 .codex/skills 都会被加载。靠近当前目录的技能也能参与。

数据流:在仓库根和 nested 子目录各写一个技能,把 cwd 设到更深的 inner → 加载 → 断言两个 Repo 技能都出现,且顺序符合加载规则。

调用关系:它依靠 project_layers_for_cwd 发现多层 .codex,再通过 load_skills_for_test 验证。

调用图:调用 4 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

loads_skills_from_codex_dir_when_not_git_repo1630–1666 ↗
async fn loads_skills_from_codex_dir_when_not_git_repo()

作用:检查不在 Git 仓库里时,当前目录自己的 .codex/skills 仍能加载。这样普通文件夹也可以有本地技能。

数据流:创建普通工作目录,在它的 .codex/skills 写技能 → 以该目录作为 cwd 加载 → 断言技能作用域是 Repo。

调用关系:它用 make_config_for_cwd 模拟非 Git 场景,验证不会完全依赖 .git。

调用图:调用 3 个内部函数(load_skills_for_test, make_config_for_cwd, write_skill_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

deduplicates_by_path_preferring_first_root1669–1711 ↗
async fn deduplicates_by_path_preferring_first_root()

作用:检查同一个技能文件通过两个根目录被扫到时,只保留第一次出现的那个。这样不会重复显示同一份技能。

数据流:在一个根目录写技能 → 用 Repo 和 User 两个 SkillRoot 指向同一根 → 加载 → 断言只出现一次,作用域采用第一个 Repo。

调用关系:它直接调用 load_skills_from_roots,绕开配置层,专门测按路径去重。

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

keeps_duplicate_names_from_repo_and_user1714–1765 ↗
async fn keeps_duplicate_names_from_repo_and_user()

作用:检查仓库和用户可以有同名技能。系统不会只因为名字一样就删掉其中一个。

数据流:在用户目录和仓库目录各写一个名为 dupe-skill 的技能 → 加载 → 断言两个技能都保留,分别是 Repo 和 User。

调用关系:它说明去重依据是路径,不是技能名;使用 make_config_for_cwd 串起完整配置。

调用图:调用 5 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, write_skill, write_skill_at);外部调用 3 个(assert!, assert_eq!, tempdir)。

keeps_duplicate_names_from_nested_codex_dirs1768–1839 ↗
async fn keeps_duplicate_names_from_nested_codex_dirs()

作用:检查同一仓库内不同 .codex 目录里的同名技能也会保留。这样不同层级可以各自定义同名技能。

数据流:在仓库根和 nested/.codex/skills 各写同名技能 → 从 nested/inner 加载 → 规范化路径并按路径顺序确定期望 → 断言两个技能都存在。

调用关系:它用 normalized 处理路径顺序差异,验证多项目层技能不会按名称互相覆盖。

调用图:调用 5 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, normalized, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

repo_skills_search_does_not_escape_repo_root1842–1868 ↗
async fn repo_skills_search_does_not_escape_repo_root()

作用:检查仓库技能搜索不会跑到仓库根外面。否则外层目录的 .codex/skills 可能被误当成本仓库技能。

数据流:在仓库外层目录写技能,在内层 repo 标记 .git → 以 repo 为 cwd 加载 → 断言没有加载外层技能。

调用关系:它用 mark_as_git_repo 固定仓库边界,守住项目范围安全。

调用图:调用 4 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

loads_skills_when_cwd_is_file_in_repo1871–1910 ↗
async fn loads_skills_when_cwd_is_file_in_repo()

作用:检查当前路径是仓库里的一个文件时,也能找到仓库技能。命令可能从文件路径上下文启动。

数据流:创建 Git 仓库和技能,再写一个普通文件作为 cwd → 生成配置并加载 → 断言仓库根的技能被加载。

调用关系:它依赖 project_layers_for_cwd 对“cwd 是文件”时取父目录的处理。

调用图:调用 4 个内部函数(load_skills_for_test, make_config_for_cwd, mark_as_git_repo, write_skill_at);外部调用 4 个(assert!, assert_eq!, write, tempdir)。

non_git_repo_skills_search_does_not_walk_parents1913–1938 ↗
async fn non_git_repo_skills_search_does_not_walk_parents()

作用:检查非 Git 目录不会一路向父目录找 .codex/skills。这样普通目录不会误吸收上层目录的技能。

数据流:在外层目录写 .codex/skills,在内层目录作为 cwd 但不创建 .git → 加载 → 断言没有技能。

调用关系:它和 loads_skills_from_codex_dir_when_not_git_repo 对照:非 Git 只看当前相关目录,不随便爬父目录。

调用图:调用 3 个内部函数(load_skills_for_test, make_config_for_cwd, write_skill_at);外部调用 4 个(assert!, assert_eq!, create_dir_all, tempdir)。

loads_skills_from_system_cache_when_present1941–1969 ↗
async fn loads_skills_from_system_cache_when_present()

作用:检查用户配置目录下的 .system 技能缓存会作为系统技能加载。系统缓存是给系统预置技能使用的位置。

数据流:在 codex_home/skills/.system 写技能,以普通工作目录加载 → 断言结果里有 System 作用域技能。

调用关系:它用 write_system_skill 造系统缓存,再通过完整测试加载流程验证。

调用图:调用 3 个内部函数(load_skills_for_test, make_config_for_cwd, write_system_skill);外部调用 3 个(assert!, assert_eq!, tempdir)。

skill_roots_include_admin_with_lowest_priority1972–1993 ↗
async fn skill_roots_include_admin_with_lowest_priority()

作用:检查管理员技能根目录会被加入,而且优先级最低。优先级低表示它排在用户、系统等来源之后。

数据流:生成默认配置 → 调用 skill_roots 获取所有根目录 → 根据是否能找到 home_dir 构造期望作用域顺序 → 断言最后包含 Admin。

调用关系:它直接测试更高层的 skill_roots 入口,确认管理员来源没有被漏掉且排在末尾。

调用图:调用 1 个内部函数(make_config);外部调用 6 个(clone, new, assert_eq!, skill_roots, tempdir, vec!)。

core-skills/src/manager_tests.rs源码 ↗
testtest run

这里的“技能”可以理解成放在某个文件夹里的小能力说明书,核心文件通常叫 SKILL.md。这个测试文件会临时造出假的用户技能、插件技能、项目技能和系统内置技能,再喂给 SkillsManager,看它的结果对不对。它重点检查几件事:缓存会不会复用,强制刷新会不会重新读磁盘;运行时额外加的技能目录会不会生效;配置里按路径或按名字禁用技能时,优先级是否正确;没有文件系统访问权限时,项目目录里的技能会不会被跳过。可以把它看成技能系统的“验收清单”:每条测试都搭一个小场景,然后确认最终加载出来的技能名单和禁用名单符合预期。

函数细节25
write_user_skill23–28 ↗
fn write_user_skill(codex_home: &TempDir, dir: &str, name: &str, description: &str)

作用:在临时的 Codex 主目录里写一个假的用户技能,用来给测试制造输入数据。它省得每个测试都重复手写建目录和写 SKILL.md 的代码。

数据流:输入一个临时主目录、技能子目录名、技能名和描述 → 它创建 skills/<dir> 文件夹,并写入带有 name 和 description 的 SKILL.md → 磁盘上多出一个可被技能管理器发现的用户技能文件。

调用关系:多个加载和缓存测试会先调用它准备用户技能;它只做文件准备,把后续验证交给 SkillsManager 的加载函数和测试里的断言。

调用图:被 4 处调用(skills_for_config_reuses_cache_for_same_effective_config, skills_for_cwd_loads_repo_and_user_roots_with_local_fs, skills_for_cwd_uses_cached_result_until_force_reload, skills_for_cwd_without_fs_skips_repo_roots);外部调用 4 个(path, format!, create_dir_all, write)。

write_plugin_skill30–56 ↗
fn write_plugin_skill(
    codex_home: &TempDir,
    marketplace: &str,
    plugin_name: &str,
    dir: &str,
    name: &str,
    description: &str,
) -> PathBuf

作用:在临时目录里伪造一个插件自带的技能。测试需要它来确认插件技能能被加载,也能按名字被禁用。

数据流:输入 Codex 主目录、市场名、插件名、技能目录、技能名和描述 → 它创建插件缓存目录、插件说明 plugin.json 和技能的 SKILL.md → 返回这个技能文件的路径,方便后面继续定位插件根目录或做断言。

调用关系:它被插件禁用测试使用;准备好插件技能后,测试再调用 plugin_skill_root_for_skill_path 生成插件根信息,然后交给技能加载流程。

调用图:被 1 处调用(skills_for_config_disables_plugin_skills_by_name);外部调用 4 个(path, format!, create_dir_all, write)。

plugin_skill_root_for_skill_path58–71 ↗
fn plugin_skill_root_for_skill_path(skill_path: &Path, plugin_id: &str) -> PluginSkillRoot

作用:根据某个插件技能文件的位置,推回这个插件的 skills 根目录和插件根目录。它让测试不用手工拼这些路径。

数据流:输入一个插件 SKILL.md 的路径和插件标识 → 它往上找父目录,得到 skills 目录和插件目录 → 输出 PluginSkillRoot,也就是技能加载器认识的插件技能入口信息。

调用关系:插件技能测试在 write_plugin_skill 写好文件后调用它;它产出的 PluginSkillRoot 会作为 effective_skill_roots 传给 skills_for_config_with_stack。

调用图:被 1 处调用(skills_for_config_disables_plugin_skills_by_name);外部调用 1 个(parent)。

test_skill73–88 ↗
fn test_skill(name: &str, path: PathBuf) -> SkillMetadata

作用:快速造一个 SkillMetadata,也就是“技能的登记卡”。这些测试只关心禁用规则,不需要真的再从文件里解析技能,所以用它直接组装测试对象。

数据流:输入技能名和 SKILL.md 路径 → 它把路径转成绝对且规范化的路径,并填入固定的测试描述、用户范围等字段 → 输出一个可参与禁用规则计算的 SkillMetadata。

调用关系:几个 disabled_paths_for_skills 测试用它把临时写好的 demo 技能变成规则引擎能看的对象;之后交给 resolve_disabled_skill_paths 判断是否禁用。

调用图:被 4 处调用(disabled_paths_for_skills_allows_name_selector_to_override_path_selector, disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill, disabled_paths_for_skills_allows_session_flags_to_override_user_layer, disabled_paths_for_skills_disables_matching_name_selectors);外部调用 1 个(abs)。

write_demo_skill90–100 ↗
fn write_demo_skill(tempdir: &TempDir) -> PathBuf

作用:写一个固定名字的演示技能,专门给禁用规则测试使用。它是一个更简单的造数据工具。

数据流:输入临时目录 → 它创建 skills/demo/SKILL.md,并写入 demo-skill 的名称和描述 → 返回这个 SKILL.md 的路径,供后续按路径配置启用或禁用。

调用关系:多个禁用规则测试先用它准备真实文件路径,再用 path_toggle_config 或 test_skill 把这个路径放进配置和技能元数据里。

调用图:被 4 处调用(disabled_paths_for_skills_allows_name_selector_to_override_path_selector, disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill, disabled_paths_for_skills_allows_session_flags_to_override_user_layer, disabled_paths_for_skills_disables_matching_name_selectors);外部调用 3 个(path, create_dir_all, write)。

user_config_layer102–112 ↗
fn user_config_layer(codex_home: &TempDir, config_toml: &str) -> ConfigLayerEntry

作用:把一段用户配置 TOML 文本包装成配置层。配置层可以理解成一张配置纸条,标明它来自用户配置文件。

数据流:输入 Codex 主目录和 TOML 字符串 → 它算出 config.toml 的绝对路径,解析 TOML 内容,并标记来源是用户配置 → 输出 ConfigLayerEntry,供配置栈使用。

调用关系:config_stack、项目技能测试等场景会用它构造用户配置层;它把原始文本变成 ConfigLayerStack 能接收的格式。

调用图:调用 2 个内部函数(new, try_from);外部调用 2 个(path, from_str)。

config_stack114–121 ↗
fn config_stack(codex_home: &TempDir, user_config_toml: &str) -> ConfigLayerStack

作用:用一段用户配置快速生成完整配置栈。配置栈就是把不同来源的配置按顺序叠起来,后面的规则可能覆盖前面的规则。

数据流:输入 Codex 主目录和用户配置文本 → 它先生成用户配置层,再创建 ConfigLayerStack → 输出一个有效的配置栈,供技能加载测试使用。

调用关系:多数技能加载测试都用它准备默认配置环境;生成的栈会传给 SkillsLoadInput 或 skills_for_config_with_stack。

调用图:调用 1 个内部函数(new);被 7 处调用(set_extra_roots_applies_to_config_loads_and_empty_clears, set_extra_roots_replaces_runtime_roots_and_clears_cache, skills_for_config_disables_plugin_skills_by_name, skills_for_config_excludes_bundled_skills_when_disabled_in_config, skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill, skills_for_config_reuses_cache_for_same_effective_config, skills_for_cwd_uses_cached_result_until_force_reload);外部调用 3 个(default, default, vec!)。

config_stack_with_session_flags123–140 ↗
fn config_stack_with_session_flags(
    codex_home: &TempDir,
    user_config_toml: &str,
    session_flags_toml: &str,
) -> ConfigLayerStack

作用:生成一个同时包含用户配置和会话参数的配置栈。会话参数可以理解成这次运行临时加的开关,通常优先级更高。

数据流:输入 Codex 主目录、用户配置文本和会话参数文本 → 它创建用户层和 SessionFlags 层,并按顺序叠成 ConfigLayerStack → 输出带临时覆盖能力的配置栈。

调用关系:它主要服务于“会话参数重新启用技能”的测试;测试用它证明同一个技能在不同有效配置下不能误用旧缓存。

调用图:调用 1 个内部函数(new);被 1 处调用(skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill);外部调用 3 个(default, default, vec!)。

path_toggle_config142–150 ↗
fn path_toggle_config(path: &std::path::Path, enabled: bool) -> String

作用:生成一小段“按文件路径启用或禁用某个技能”的配置文本。这样测试能清楚表达:我要控制这个具体文件里的技能。

数据流:输入技能路径和 enabled 布尔值 → 它拼出 [[skills.config]] 配置块,里面写 path 和 enabled → 输出 TOML 字符串,之后会被解析进配置层。

调用关系:多个禁用规则测试和缓存测试会调用它制造配置;这些配置随后被 user_config_layer、config_stack 或 ConfigLayerEntry 解析使用。

调用图:被 4 处调用(disabled_paths_for_skills_allows_name_selector_to_override_path_selector, disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill, disabled_paths_for_skills_allows_session_flags_to_override_user_layer, skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill);外部调用 1 个(format!)。

name_toggle_config152–159 ↗
fn name_toggle_config(name: &str, enabled: bool) -> String

作用:生成一小段“按技能名字启用或禁用”的配置文本。它用于测试不靠文件路径、只靠名字也能控制技能。

数据流:输入技能名和 enabled 布尔值 → 它拼出包含 name 和 enabled 的 TOML 配置块 → 输出字符串,后续放进配置栈。

调用关系:插件技能按名字禁用、名字选择器覆盖路径选择器等测试会用它;它产出的文本最终交给配置规则解析函数。

调用图:被 3 处调用(disabled_paths_for_skills_allows_name_selector_to_override_path_selector, disabled_paths_for_skills_disables_matching_name_selectors, skills_for_config_disables_plugin_skills_by_name);外部调用 1 个(format!)。

skills_for_config_with_stack161–176 ↗
async fn skills_for_config_with_stack(
    skills_manager: &SkillsManager,
    cwd: &TempDir,
    config_layer_stack: &ConfigLayerStack,
    effective_skill_roots: &[PluginSkillRoot],
) -> SkillLoadOu

作用:这是测试里的小封装,用指定配置栈去请求技能管理器加载技能。它让测试少写重复的 SkillsLoadInput 组装代码。

数据流:输入 SkillsManager、当前工作目录、配置栈和插件技能根列表 → 它组装 SkillsLoadInput,计算内置技能是否启用,并带上本地文件系统调用 skills_for_config → 输出 SkillLoadOutcome,也就是加载到的技能、错误和禁用信息。

调用关系:很多异步测试都会通过它进入真正的技能加载流程;它把准备好的配置和路径交给 SkillsManager::skills_for_config。

调用图:调用 2 个内部函数(new, skills_for_config);被 6 处调用(set_extra_roots_applies_to_config_loads_and_empty_clears, skills_for_config_disables_plugin_skills_by_name, skills_for_config_excludes_bundled_skills_when_disabled_in_config, skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill, skills_for_config_reuses_cache_for_same_effective_config, skills_for_cwd_uses_cached_result_until_force_reload);外部调用 4 个(clone, path, clone, to_vec)。

new_with_disabled_bundled_skills_removes_stale_cached_system_skills179–195 ↗
fn new_with_disabled_bundled_skills_removes_stale_cached_system_skills()

作用:检查当系统内置技能被关闭时,启动 SkillsManager 会清掉旧的系统技能缓存目录。这样旧文件不会在禁用后偷偷继续生效。

数据流:测试先在临时主目录里造一个旧的 skills/.system/stale-skill → 用 bundled_skills_enabled=false 创建 SkillsManager → 最后确认 skills/.system 已不存在。

调用关系:这是测试运行器直接执行的单元测试;它主要覆盖 SkillsManager::new 在启动时的清理行为。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert!, create_dir_all, write, tempdir)。

skills_for_config_reuses_cache_for_same_effective_config198–222 ↗
async fn skills_for_config_reuses_cache_for_same_effective_config()

作用:确认同一份有效配置下,skills_for_config 会复用缓存,而不是每次都重新扫磁盘。这样可以提高速度,但也要保证行为可预期。

数据流:先写入 skill-a 并加载一次 → 再写入 skill-b,但配置没有变化 → 第二次加载仍返回第一次的结果,说明缓存被复用,新加文件暂时看不到。

调用关系:测试用 config_stack、write_user_skill 和 skills_for_config_with_stack 搭场景;真正被验证的是 SkillsManager 的配置感知缓存。

调用图:调用 4 个内部函数(new, config_stack, skills_for_config_with_stack, write_user_skill);外部调用 3 个(assert!, assert_eq!, tempdir)。

set_extra_roots_replaces_runtime_roots_and_clears_cache225–294 ↗
async fn set_extra_roots_replaces_runtime_roots_and_clears_cache()

作用:检查运行时额外技能目录可以被替换,并且替换时会清缓存。否则旧目录里的技能可能继续残留。

数据流:先在没有额外目录时加载,确认 runtime-skill 不存在 → 设置一个包含 runtime-skill 的额外 skills 根目录并再次加载,确认它出现 → 再把额外目录换成不存在的目录,确认 runtime-skill 消失且没有错误。

调用关系:测试直接调用 SkillsManager::set_extra_roots 和 skills_for_cwd;它验证运行时目录变化会影响后续按当前目录加载的结果。

调用图:调用 3 个内部函数(new, new, config_stack);外部调用 8 个(clone, new, assert!, assert_eq!, create_dir_all, write, tempdir, vec!)。

set_extra_roots_applies_to_config_loads_and_empty_clears297–344 ↗
async fn set_extra_roots_applies_to_config_loads_and_empty_clears()

作用:确认运行时额外技能目录不只影响按当前目录加载,也影响按配置加载;同时设置为空能清掉这些额外目录。

数据流:先加载一次确认 runtime-skill 不在结果里 → 设置额外技能根并写入 runtime-skill,重新按配置加载后能看到它 → 再把额外根设为空,加载结果里又没有它。

调用关系:它通过 skills_for_config_with_stack 调用 SkillsManager::skills_for_config;重点证明 set_extra_roots 对配置加载路径也生效。

调用图:调用 3 个内部函数(new, config_stack, skills_for_config_with_stack);外部调用 6 个(new, assert!, create_dir_all, write, tempdir, vec!)。

skills_for_config_disables_plugin_skills_by_name347–392 ↗
async fn skills_for_config_disables_plugin_skills_by_name()

作用:检查插件技能可以用“插件名:技能名”这种名字选择器禁用。这样用户不用知道插件技能的真实文件路径,也能关掉它。

数据流:测试先写一个插件技能 sample-search → 配置里按名字 sample:sample-search 设置 enabled=false → 加载后技能仍被发现,但它的路径出现在 disabled_paths 中,并且不会被列入可自动调用的技能。

调用关系:它使用 write_plugin_skill、plugin_skill_root_for_skill_path、name_toggle_config 和 skills_for_config_with_stack;最终验证配置规则和插件技能命名的配合。

调用图:调用 6 个内部函数(new, config_stack, name_toggle_config, plugin_skill_root_for_skill_path, skills_for_config_with_stack, write_plugin_skill);外部调用 4 个(assert!, assert_eq!, canonicalize, tempdir)。

skills_for_cwd_loads_repo_and_user_roots_with_local_fs395–455 ↗
async fn skills_for_cwd_loads_repo_and_user_roots_with_local_fs()

作用:检查在有本地文件系统访问能力时,按当前目录加载会同时读用户技能和项目里的技能。项目技能通常放在当前仓库的 .codex/skills 下。

数据流:测试写一个用户技能和一个仓库技能 → 构造包含用户层和项目层的配置栈,并传入 LOCAL_FS → 加载结果应同时包含 user-skill 和 repo-skill,且没有错误。

调用关系:它直接调用 SkillsManager::skills_for_cwd;通过传入本地文件系统,验证仓库目录扫描这条路径会被启用。

调用图:调用 4 个内部函数(new, new, new, write_user_skill);外部调用 9 个(clone, default, new, assert!, default, create_dir_all, write, tempdir, vec!)。

skills_for_cwd_without_fs_skips_repo_roots458–514 ↗
async fn skills_for_cwd_without_fs_skips_repo_roots()

作用:检查没有文件系统对象时,技能加载会跳过项目目录里的技能,但仍能加载用户目录里的技能。这样在受限环境里不会乱访问当前项目文件。

数据流:测试同样写用户技能和仓库技能 → 调用 skills_for_cwd 时把 fs 传 None → 结果包含 user-skill,但不包含 repo-skill,并且不报错。

调用关系:它和上一条测试形成对照;同样走 SkillsManager::skills_for_cwd,只是去掉 LOCAL_FS,验证受限模式下的行为。

调用图:调用 4 个内部函数(new, new, new, write_user_skill);外部调用 8 个(default, new, assert!, default, create_dir_all, write, tempdir, vec!)。

skills_for_config_excludes_bundled_skills_when_disabled_in_config517–556 ↗
async fn skills_for_config_excludes_bundled_skills_when_disabled_in_config()

作用:检查配置明确关闭内置技能时,系统内置技能目录不会参与加载。这样用户关闭内置能力后,不会因为缓存文件还在而被加载出来。

数据流:测试先造一个 skills/.system/bundled-skill,并在配置中设置 skills.bundled.enabled=false → 创建管理器后又重新写回目录,避免只靠启动清理通过测试 → 加载后确认没有 bundled-skill,也没有 System 范围的技能。

调用关系:它用 config_stack 和 skills_for_config_with_stack 进入配置加载流程;验证的是根目录选择逻辑,而不只是目录删除逻辑。

调用图:调用 3 个内部函数(new, config_stack, skills_for_config_with_stack);外部调用 4 个(assert!, create_dir_all, write, tempdir)。

skills_for_cwd_uses_cached_result_until_force_reload559–617 ↗
async fn skills_for_cwd_uses_cached_result_until_force_reload()

作用:确认按当前目录加载时,默认会用缓存;只有 force_reload 为 true 才会重新扫描。这样既能省时间,也给调用方一个明确的刷新按钮。

数据流:先加载一次建立缓存,并确认 late-skill 不存在 → 写入 late-skill 后不强制刷新再加载,仍看不到它 → 设置 force_reload=true 后再次加载,终于能看到 late-skill。

调用关系:它先用 skills_for_config_with_stack 预热相关缓存,再直接调用 SkillsManager::skills_for_cwd;重点验证 force_reload 参数的作用。

调用图:调用 5 个内部函数(new, new, config_stack, skills_for_config_with_stack, write_user_skill);外部调用 4 个(clone, new, assert!, tempdir)。

disabled_paths_for_skills_allows_session_flags_to_override_user_layer621–652 ↗
fn disabled_paths_for_skills_allows_session_flags_to_override_user_layer()

作用:检查会话参数能覆盖用户配置,把用户禁用的技能重新启用。这里的会话参数就是本次运行临时传入的更高优先级配置。

数据流:先写 demo 技能 → 用户层按路径设置 enabled=false,会话层按同一路径设置 enabled=true → 解析规则后计算禁用路径,结果为空,表示技能没有被禁用。

调用关系:它使用 write_demo_skill、test_skill、path_toggle_config 和 skill_config_rules_from_stack;最后把技能和规则交给 resolve_disabled_skill_paths。

调用图:调用 7 个内部函数(new, new, skill_config_rules_from_stack, path_toggle_config, test_skill, write_demo_skill, try_from);外部调用 6 个(default, assert_eq!, default, tempdir, from_str, vec!)。

disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill656–690 ↗
fn disabled_paths_for_skills_allows_session_flags_to_disable_user_enabled_skill()

作用:检查会话参数也能反过来禁用用户配置中启用的技能。它证明高优先级配置不是只会“打开”,也能“关闭”。

数据流:先写 demo 技能 → 用户层按路径 enabled=true,会话层按同一路径 enabled=false → 解析并计算后,禁用集合里包含这个技能的规范化路径。

调用关系:它和上一条测试是反向场景;同样通过 skill_config_rules_from_stack 生成规则,再由 resolve_disabled_skill_paths 判断最终禁用结果。

调用图:调用 7 个内部函数(new, new, skill_config_rules_from_stack, path_toggle_config, test_skill, write_demo_skill, try_from);外部调用 6 个(default, assert_eq!, default, tempdir, from_str, vec!)。

disabled_paths_for_skills_disables_matching_name_selectors694–723 ↗
fn disabled_paths_for_skills_disables_matching_name_selectors()

作用:检查按名字写的禁用规则能命中同名技能。这样即使路径不方便写,用户也可以用技能名控制开关。

数据流:先写 demo 技能,但把测试元数据里的名字设成 github:yeet → 配置里按 name=github:yeet 设置 enabled=false → 计算结果返回这个技能路径,表示它被禁用。

调用关系:它用 name_toggle_config 生成名字规则,用 test_skill 生成技能对象;最终验证 resolve_disabled_skill_paths 对名字选择器的处理。

调用图:调用 7 个内部函数(new, new, skill_config_rules_from_stack, name_toggle_config, test_skill, write_demo_skill, try_from);外部调用 6 个(default, assert_eq!, default, tempdir, from_str, vec!)。

disabled_paths_for_skills_allows_name_selector_to_override_path_selector727–758 ↗
fn disabled_paths_for_skills_allows_name_selector_to_override_path_selector()

作用:检查名字选择器在更高优先级层里可以覆盖路径选择器。简单说,用户配置按路径关掉,临时参数按名字打开,最后应该打开。

数据流:先写一个名为 github:yeet 的测试技能 → 用户层按路径禁用它,会话层按名字启用它 → 规则解析后计算禁用路径,结果为空。

调用关系:它同时使用 path_toggle_config 和 name_toggle_config;通过配置栈层级顺序验证不同选择方式之间的覆盖规则。

调用图:调用 8 个内部函数(new, new, skill_config_rules_from_stack, name_toggle_config, path_toggle_config, test_skill, write_demo_skill, try_from);外部调用 6 个(default, assert_eq!, default, tempdir, from_str, vec!)。

skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill762–811 ↗
async fn skills_for_config_ignores_cwd_cache_when_session_flags_reenable_skill()

作用:检查当会话参数改变了技能启用状态时,按配置加载不能误用旧的当前目录缓存。否则父流程里禁用的技能,子流程临时启用后仍可能显示为禁用。

数据流:先写 demo-skill → 父配置按路径禁用它,并用 skills_for_cwd 强制加载,确认它是禁用状态 → 子配置加上会话层把它启用,再用 skills_for_config 加载,确认同一个技能变成启用状态。

调用关系:它使用 config_stack、config_stack_with_session_flags、path_toggle_config 和 skills_for_config_with_stack;核心验证 SkillsManager 在有效配置不同时不会错误复用 cwd 缓存。

调用图:调用 6 个内部函数(new, new, config_stack, config_stack_with_session_flags, path_toggle_config, skills_for_config_with_stack);外部调用 6 个(clone, new, assert_eq!, create_dir_all, write, tempdir)。

ext/skills/tests/executor_file_system_authority.rs源码 ↗
testtest run

这份测试文件造了一个假的文件系统 SyntheticFileSystem。它像一个临时搭出来的小书架:外面看是 alias 路径,真正放书的位置是 canonical 路径,里面只有一个 skill/SKILL.md 文件。第一个测试确认技能加载器会通过传进来的执行器文件系统读目录和文件,而不是直接碰本机磁盘;因为测试里真实路径根本不存在,如果还能读到技能,就说明走对了通道。第二个测试确认即使两个执行器根目录指向同一个真实路径,只要它们的 root id 不同,展示出来的 skill:// 地址也要带不同 id。这样权限边界不会糊掉,就像两张门禁卡都能进同一栋楼,也不能把卡号当成同一个。

函数细节13
SyntheticFileSystem::metadata75–93 ↗
fn metadata(&self, path: &AbsolutePathBuf) -> io::Result<FileMetadata>

作用:这个函数回答“这个路径是什么东西”:是目录、是文件,还是根本不存在。测试里的假文件系统靠它来假装自己有一个技能目录和一个 SKILL.md 文件。

数据流:进去的是一个绝对路径 → 它把路径和预设的 canonical 根目录、skill 目录、SKILL.md 文件逐个对比 → 如果匹配,就返回一份文件信息;如果不匹配,就返回“找不到”。它不改动任何真实文件。

调用关系:它是 SyntheticFileSystem 的基础判断工具。canonicalize 会先用它确认路径存在,get_metadata 也会直接把外部询问转给它。

调用图:调用 1 个内部函数(join);被 2 处调用(canonicalize, get_metadata);外部调用 1 个(new)。

SyntheticFileSystem::canonicalize97–103 ↗
fn canonicalize(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, PathUri>

作用:这个函数把传进来的路径变成“标准路径”。在这个测试里,它故意把 alias 根目录改写成 canonical 根目录,用来验证加载器会尊重执行器给出的标准位置。

数据流:进去的是一个 PathUri,也就是带路径含义的地址 → 它先转成绝对路径;如果正好是 alias 根目录,就返回 canonical 根目录;否则先检查这个路径在假文件系统里是否存在,再原样转回 PathUri → 出来的是标准化后的路径,或一个错误。

调用关系:技能加载流程调用文件系统的 canonicalize 时会走到这里。它会用 SyntheticFileSystem::metadata 做存在性检查,然后把结果交还给加载器继续找技能文件。

调用图:调用 3 个内部函数(metadata, from_abs_path, to_abs_path);外部调用 1 个(pin)。

SyntheticFileSystem::read_file105–111 ↗
fn read_file(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, Vec<u8>>

作用:这个函数只允许读取那一个假的技能文件 skill/SKILL.md。它用来证明技能正文确实是从执行器文件系统读出来的。

数据流:进去的是要读取的路径 → 它把路径转成绝对路径,并检查是否等于 canonical_root/skill/SKILL.md → 如果是,就吐出预设的 SKILL_CONTENTS 字节内容;否则返回“找不到”。

调用关系:当 HostLoadedSkills 后面读取技能正文时,会通过执行器文件系统来到这里。它不调用真实磁盘,只返回测试提前写死的内容。

调用图:调用 2 个内部函数(join, to_abs_path);外部调用 2 个(pin, new)。

SyntheticFileSystem::read_file_stream113–124 ↗
fn read_file_stream(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream>

作用:这个函数表示“流式读取文件”在这个假文件系统里不支持。流式读取可以理解成一边读一边拿数据,适合大文件,但这个测试不需要它。

数据流:进去的是路径和沙箱信息,但这里都不使用 → 它直接构造一个“不支持”的错误 → 出来永远是失败结果,没有任何文件被读取。

调用关系:它是为了完整实现 ExecutorFileSystem 这个接口而存在。当前这些测试不会依赖流式读取,所以它明确拒绝,避免误用。

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

SyntheticFileSystem::write_file126–133 ↗
fn write_file(
        &'a self,
        _path: &'a PathUri,
        _contents: Vec<u8>,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, ()>

作用:这个函数拒绝写文件。这个假文件系统是只读的,只用来测试技能加载,不允许测试代码意外改东西。

数据流:进去的是目标路径、要写入的内容和沙箱信息 → 它全部忽略 → 直接返回“只读”的不支持错误,不产生任何写入。

调用关系:它补齐 ExecutorFileSystem 接口里的写入能力,但在本测试场景中不该被调用。如果有人调用,错误会马上暴露出流程走偏了。

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

SyntheticFileSystem::create_directory135–142 ↗
fn create_directory(
        &'a self,
        _path: &'a PathUri,
        _options: CreateDirectoryOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'

作用:这个函数拒绝创建目录。测试里的文件结构是固定的,不能在运行中被改变。

数据流:进去的是目录路径、创建选项和沙箱信息 → 它不使用这些输入 → 直接返回“只读”的错误,目录不会被创建。

调用关系:它也是为了实现完整文件系统接口而提供的保护口。技能加载只应该读目录和读文件,不应该创建目录。

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

SyntheticFileSystem::get_metadata144–150 ↗
fn get_metadata(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, FileMetadata>

作用:这个函数把外部的“查一下这个路径信息”请求接进来。它返回路径是文件还是目录等基本信息。

数据流:进去的是 PathUri 路径 → 它转成绝对路径 → 交给 SyntheticFileSystem::metadata 判断 → 出来是一份 FileMetadata,或者“找不到”的错误。

调用关系:技能加载器如果通过 ExecutorFileSystem 查询文件状态,就会走这个接口方法。真正判断逻辑集中在 SyntheticFileSystem::metadata。

调用图:调用 2 个内部函数(metadata, to_abs_path);外部调用 1 个(pin)。

SyntheticFileSystem::read_directory152–158 ↗
fn read_directory(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>>

作用:这个函数列出假文件系统里的目录内容。它让加载器看到根目录下有 skill 文件夹,skill 文件夹里有 SKILL.md。

数据流:进去的是目录路径 → 它转成绝对路径;如果是 canonical 根目录,就返回一个名叫 skill 的目录;如果是 canonical_root/skill,就返回一个名叫 SKILL.md 的文件;其他路径返回“找不到” → 它不碰真实磁盘。

调用关系:技能加载器扫描技能根目录时会用到它。它给加载器提供一棵很小但完整的目录树,让后续 read_file 能读到技能内容。

调用图:调用 2 个内部函数(join, to_abs_path);外部调用 3 个(pin, new, vec!)。

SyntheticFileSystem::remove160–167 ↗
fn remove(
        &'a self,
        _path: &'a PathUri,
        _options: RemoveOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, ()>

作用:这个函数拒绝删除文件或目录。假文件系统是只读测试道具,不允许破坏里面的固定结构。

数据流:进去的是要删除的路径、删除选项和沙箱信息 → 它不使用这些输入 → 直接返回“只读”的错误,没有任何东西被删除。

调用关系:它只是补齐 ExecutorFileSystem 接口。正常的技能加载流程不应该调用删除操作;如果调用了,就说明流程不符合测试预期。

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

SyntheticFileSystem::copy169–177 ↗
fn copy(
        &'a self,
        _source_path: &'a PathUri,
        _destination_path: &'a PathUri,
        _options: CopyOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> Ex

作用:这个函数拒绝复制文件。这个测试只关心读技能,不关心复制或改动文件。

数据流:进去的是来源路径、目标路径、复制选项和沙箱信息 → 它全部忽略 → 返回“只读”的错误,不产生任何复制结果。

调用关系:它用于完整实现文件系统接口,但不是测试主线的一部分。它的存在让假文件系统更像真实接口,同时把不该发生的写类操作挡住。

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

skill_loading_and_reads_use_the_supplied_executor_file_system181–216 ↗
async fn skill_loading_and_reads_use_the_supplied_executor_file_system()

作用:这是第一个测试,确认技能加载和技能正文读取都使用传入的执行器文件系统。它防止代码绕过执行器,直接去真实磁盘找文件。

数据流:开始时它在临时目录下拼出 alias 和 canonical 两个路径,并确认这两个路径在真实磁盘上都不存在 → 然后把 alias 作为技能根目录,把 SyntheticFileSystem 塞给加载器 → 加载器扫描后应该得到一个名叫 synthetic 的技能,路径应变成 canonical_root/skill/SKILL.md → 最后再读取技能正文,应该读到预设的 SKILL_CONTENTS。

调用关系:它直接调用 load_skills_from_roots 启动加载流程,过程中依赖 SyntheticFileSystem 的 canonicalize、read_directory、read_file 等接口。加载完成后,它用 HostLoadedSkills 再读一次正文,验证读取阶段也没有绕开执行器文件系统。

调用图:调用 3 个内部函数(load_skills_from_roots, new, from_absolute_path_checked);外部调用 5 个(new, assert!, assert_eq!, format!, temp_dir)。

selected_root_id_distinguishes_identical_executor_paths219–282 ↗
async fn selected_root_id_distinguishes_identical_executor_paths()

作用:这是第二个测试,确认两个执行器根目录就算指向同一个本地路径,也会因为 root id 不同而被区分开。它保护的是权限和来源身份,不只是文件路径本身。

数据流:开始时它调用 create_local_skill_root 在真实临时目录里建一个技能 → 然后准备两个 SelectedCapabilityRoot,它们路径相同,但 id 分别是 root-a 和 root-b → 交给 ExecutorSkillProvider 列技能 → 出来的两个目录项都指向同一个 canonical 路径,但 display_path 里的 skill:// 前缀分别带 root-a 和 root-b → 最后删除临时目录。

调用关系:它通过 ExecutorSkillProvider::new_with_restriction_product 创建技能提供者,再调用 provider.list 走完整的列技能流程。create_local_skill_root 给它准备测试数据;断言部分检查 provider 没有把两个 root id 合并掉。

调用图:调用 4 个内部函数(default_for_tests, new_with_restriction_product, create_local_skill_root, from_absolute_path_checked);外部调用 3 个(new, assert_eq!, remove_dir_all)。

create_local_skill_root284–294 ↗
fn create_local_skill_root(label: &str) -> io::Result<std::path::PathBuf>

作用:这个小工具函数给测试创建一个真实的临时技能目录。它把 SKILL.md 写到磁盘上,让第二个测试可以模拟本地存在的技能根目录。

数据流:进去的是一个 label,用来放进目录名里方便区分 → 它加上当前进程号和递增编号,拼出一个临时目录路径 → 创建 skill 子目录,并写入 SKILL.md 内容 → 返回这个测试根目录路径,或返回文件操作错误。

调用关系:它被 selected_root_id_distinguishes_identical_executor_paths 调用,用来准备测试前置数据。测试结束后,调用者负责把这个目录删掉。

调用图:被 1 处调用(selected_root_id_distinguishes_identical_executor_paths);外部调用 4 个(format!, temp_dir, create_dir_all, write)。

ext/skills/tests/skills_extension.rs源码 ↗
testtest run

可以把这个文件看成技能扩展的“验收清单”。技能就是一段可被模型调用的说明文件,比如用户输入 $lint-fix 时,系统要知道去哪里找这个技能、要不要把它展示在可选列表里、是否该把全文塞进本轮对话。测试里造了假的技能提供者 StaticSkillProvider,不真的连网络或外部环境,而是返回固定目录和固定内容。这样测试能专心检查扩展本身:启动线程时是否建立技能目录;上下文提示里是否列出该列的技能;用户输入提到技能时是否读取正确文件;提供者失败时是否只警告一次并缓存失败;隐藏技能是否不出现在列表但仍能被直接调用。ChannelEventSink 像一个收信箱,用来接住扩展发出的警告事件,方便测试确认错误提示没有丢。

函数细节14
installed_extension_uses_host_loaded_skills55–138 ↗
async fn installed_extension_uses_host_loaded_skills() -> TestResult

作用:这个测试确认:如果主程序已经加载好了用户本地的技能,技能扩展会直接使用这些技能,并把技能目录和技能正文正确注入到对话里。

数据流:它先创建一个临时的 Codex 主目录,在里面写入一个 demo 技能文件;然后安装技能扩展、模拟线程启动,再把“主程序已加载的技能”放进本轮数据仓库。接着它模拟用户输入 $demo。结果应该出来两段提示:一段给开发者看的技能目录,一段给用户角色的 demo 技能全文;同时本轮仓库里会留下一个标记,说明这个技能路径已经被注入过。最后它删除临时目录。

调用关系:这是对真实安装流程的端到端检查。它会用到 default_config 取得测试配置,用 test_codex_home 准备隔离目录,再通过扩展注册表触发线程启动和本轮输入贡献流程。它验证的是主程序预加载技能和技能扩展之间的衔接。

调用图:调用 6 个内部函数(new, new, new, default_config, test_codex_home, try_from);外部调用 12 个(clone, new, new, assert!, assert_eq!, install, default, format!, create_dir_all, remove_dir_all (+2 more))。

selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_input141–256 ↗
async fn selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_input() -> TestResult

作用:这个测试确认:被选中的执行环境技能会先出现在上下文目录里,而真正的技能正文只在用户点名它时才加入本轮输入。

数据流:它先做一个假的执行器技能提供者,里面只有 lint-fix 技能;再在线程数据里放入已选择的能力根目录,表示这个技能所在的环境资源被选中了。线程启动后,它先请求上下文提示,应该看到 lint-fix 的目录项。之后模拟用户输入 $lint-fix please,扩展才读取技能正文并生成一段用户消息。再下一轮如果用户没提技能,就不会产生额外内容。

调用关系:这个测试把 StaticSkillProvider 放进 SkillProviders,再安装到扩展注册表里。它先走上下文贡献者,再走本轮输入贡献者,并用 read_request_keys 检查扩展最终请求读取的是那个被选中的执行器技能。

调用图:调用 4 个内部函数(new, new, new, default_config);外部调用 8 个(clone, new, new, new, assert!, assert_eq!, install_with_providers, vec!)。

orchestrator_catalog_snapshot_caches_failure259–329 ↗
async fn orchestrator_catalog_snapshot_caches_failure() -> TestResult

作用:这个测试确认:编排器技能目录第一次读取失败后,扩展会记住这次失败,不会在同一线程里每轮都反复请求、反复报错。

数据流:它创建一个假的编排器技能提供者,并设置第一次列目录时故意失败。测试还放了一个事件接收器,用来收警告。线程启动后请求上下文,结果没有技能提示,并收到一条“技能不可用”的警告。随后连续两轮用户都输入 $first,扩展也不会再读目录或注入技能。最后检查列目录的调用次数仍然只有一次。

调用关系:这个测试使用 ChannelEventSink 接住扩展发出的警告事件,用 StaticSkillProvider 模拟第一次失败。它主要覆盖扩展的缓存策略:失败发生在上下文贡献阶段,之后本轮输入阶段也沿用这个失败快照。

调用图:调用 4 个内部函数(with_event_sink, new, new, default_config);外部调用 11 个(clone, new, new, new, new, assert!, assert_eq!, install_with_providers, panic!, channel (+1 more))。

root_qualified_locator_selects_only_the_matching_executor_skill332–416 ↗
async fn root_qualified_locator_selects_only_the_matching_executor_skill() -> TestResult

作用:这个测试确认:如果两个不同能力根目录里都有同名技能,用户带着完整定位路径点名时,扩展只会读取匹配那个根目录的技能。

数据流:它造出两个都叫 lint-fix 的技能,一个来自 root-a,一个来自 root-b,并把两个根目录都标记为已选择。然后用户输入的是一个 Mention,也就是带名字和完整路径的点名,路径指向 root-b。结果扩展只生成一段技能内容,而且读取请求里记录的是 root-b 的技能,不会误读 root-a。

调用关系:这个测试依赖 StaticSkillProvider 提供两个同名目录项,使用扩展的本轮输入贡献者处理带路径的用户输入。最后通过 read_request_keys 验证选择逻辑没有只按名字匹配,而是同时看了根目录和资源定位。

调用图:调用 4 个内部函数(new, new, new, default_config);外部调用 8 个(clone, new, new, new, assert!, assert_eq!, install_with_providers, vec!)。

prompt_hidden_skill_can_still_be_invoked419–493 ↗
async fn prompt_hidden_skill_can_still_be_invoked() -> TestResult

作用:这个测试确认:有些技能可以从技能列表提示里隐藏,但用户如果明确输入它的名字,仍然能调用它。

数据流:它创建一个主机技能提供者,里面有 visible-skill 和 hidden-skill,其中 hidden-skill 被标记为不显示在提示目录里。线程启动后,用户输入 $hidden-skill。结果生成两段内容:第一段技能目录只包含 visible-skill,不包含 hidden-skill;第二段则是 hidden-skill 的正文。读取记录也显示确实读了 hidden-skill。

调用关系:这个测试检查目录展示和实际调用不是一回事。StaticSkillProvider 提供带隐藏标记的目录项,扩展的本轮输入贡献者既要生成可见目录,又要按用户点名读取隐藏技能。

调用图:调用 4 个内部函数(new, new, new, default_config);外部调用 8 个(clone, new, new, new, assert!, assert_eq!, install_with_providers, vec!)。

ChannelEventSink::emit506–508 ↗
fn emit(&self, event: Event)

作用:这个函数是测试用的“事件收件箱入口”。扩展发出警告或其他事件时,它把事件塞进一个通道里,方便测试代码稍后检查。

数据流:输入是一条 Event,也就是扩展想发出去的事件。函数把它发送到内部保存的 mpsc 通道发送端;如果接收端已经没了,发送失败也会被忽略,不会让测试流程崩掉。它不返回有用结果,只改变通道里的待接收事件。

调用关系:它实现了 ExtensionEventSink 这个接口,所以扩展注册表可以把它当成正常事件出口使用。在 orchestrator_catalog_snapshot_caches_failure 里,扩展通过它发出失败警告,测试再从通道另一头读取并断言内容。

StaticSkillProvider::list512–526 ↗
fn list(&self, _query: SkillListQuery) -> SkillProviderFuture<'_, SkillCatalog>

作用:这个函数是假的技能目录查询。测试用它假装某个技能来源能列出自己有哪些技能,也能模拟第一次查询失败。

数据流:输入是一个目录查询请求,但这里不关心请求细节。函数会先记录调用次数;如果配置成“第一次失败”且这正是第一次调用,就返回一个临时失败错误。否则它返回预先放好的 SkillCatalog,也就是技能目录快照。

调用关系:它实现 SkillProvider 的 list 能力,供扩展在启动后构建技能目录时调用。多个测试用它提供固定目录;orchestrator_catalog_snapshot_caches_failure 特别用它的失败开关和调用计数来检查扩展是否缓存失败。

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

StaticSkillProvider::read528–540 ↗
fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult>

作用:这个函数是假的技能正文读取器。测试用它确认扩展到底要求读取了哪个技能,并返回一段固定的技能内容。

数据流:输入是 SkillReadRequest,里面有技能来源、包编号、资源编号。函数先把这次请求复制一份放进共享列表,方便测试之后检查;然后返回一个 SkillReadResult,资源编号沿用请求里的资源,内容固定为 lint-fix 的说明文字。

调用关系:它实现 SkillProvider 的 read 能力,只有当扩展认为某个技能被用户调用时才会走到这里。相关测试之后会调用 read_request_keys,把它记录下来的请求整理出来,用来验证扩展没有读错技能。

调用图:外部调用 3 个(clone, pin, clone)。

test_entry547–562 ↗
fn test_entry(
    kind: SkillSourceKind,
    authority_id: &str,
    package_id: &str,
    main_prompt: &str,
) -> SkillCatalogEntry

作用:这个辅助函数快速造出一个测试用的技能目录项,避免每个测试都手写一堆重复字段。

数据流:输入包括技能来源类型、来源编号、包编号和主提示文件路径。函数从包编号最后一段推导出技能名,填入固定描述“Fix lint errors.”,再组装成 SkillCatalogEntry,并设置一个显示用的 skill:// 路径。输出就是可放进测试目录的技能条目。

调用关系:多个测试用它搭建 StaticSkillProvider 的目录数据。它本身不碰扩展运行流程,只是让测试数据更短、更一致。

调用图:调用 3 个内部函数(new, new, new);外部调用 2 个(new, format!)。

default_config570–575 ↗
fn default_config() -> TestConfig

作用:这个辅助函数给测试提供一份默认配置:包含技能说明,并启用内置技能。

数据流:它不需要输入。调用后返回一个 TestConfig,其中 include_instructions 和 bundled_skills_enabled 都是 true;不会读取文件,也不会改变外部状态。

调用关系:所有主要测试都会先调用它,再把结果交给线程启动流程。之后 skills_extension_config 会把这份测试配置转换成扩展真正需要的配置形状。

调用图:被 5 处调用(installed_extension_uses_host_loaded_skills, orchestrator_catalog_snapshot_caches_failure, prompt_hidden_skill_can_still_be_invoked, root_qualified_locator_selects_only_the_matching_executor_skill, selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_input)。

skills_extension_config577–582 ↗
fn skills_extension_config(config: &TestConfig) -> SkillsExtensionConfig

作用:这个函数把测试自己的配置格式转换成技能扩展实际使用的配置格式。

数据流:输入是 TestConfig,里面说明是否加入技能说明、是否启用内置技能。函数把这两个布尔值原样拷贝进 SkillsExtensionConfig 并返回;它不做额外判断,也不修改输入。

调用关系:安装技能扩展时会把这个函数作为配置转换器交进去。这样测试可以用简单的 TestConfig,但扩展运行时仍拿到它熟悉的 SkillsExtensionConfig。

test_codex_home584–590 ↗
fn test_codex_home() -> PathBuf

作用:这个辅助函数为测试生成一个临时的 Codex 主目录路径,避免不同测试共用同一个目录而互相污染。

数据流:它不接收输入。函数从一个全局计数器取下一个编号,再结合当前进程号和系统临时目录,拼出一个唯一的目录路径并返回;它只生成路径,不负责创建目录。

调用关系:installed_extension_uses_host_loaded_skills 用它准备一个隔离的本地技能目录。测试结束后会手动删除这个目录,保持环境干净。

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

read_request_keys592–607 ↗
fn read_request_keys(
    requests: &Arc<Mutex<Vec<SkillReadRequest>>>,
) -> Vec<(SkillAuthority, SkillPackageId, SkillResourceId)>

作用:这个辅助函数把假的技能提供者记录的读取请求整理成易比较的三元组,方便测试断言“读的是不是正确技能”。

数据流:输入是一个带互斥锁的请求列表;互斥锁可以理解成一把锁,防止同时读写同一份列表。函数加锁后遍历每条 SkillReadRequest,取出来源、包编号和资源编号,组成一个新列表返回;原列表不被清空。

调用关系:凡是需要确认扩展没有读错技能的测试,都会在触发本轮输入贡献后调用它。它配合 StaticSkillProvider::read 使用:read 负责记录原始请求,这个函数负责把记录变成断言好比对的形状。

扩展 API 与内置扩展

本组先验证通用扩展框架,再检查具体扩展及其端到端运行时行为。

ext/extension-api/tests/capabilities.rs源码 ↗
testtest

这个文件不是真正给用户运行功能的,而是测试代码。它像验收清单一样,检查扩展接口里的两个承诺。第一,NoopResponseItemInjector 是一个“什么都不加”的注入器:如果有人想让它在同一轮对话里偷偷塞入回复内容,它应该拒绝,并把原来的输入原封不动还回来,避免数据被意外改写。第二,AgentSpawner 允许用一个普通闭包来当“子代理启动器”。测试会记录传进去的线程编号和请求文本,再确认闭包真的收到了这些参数,并且异步返回了请求长度。这里用到互斥锁(Mutex,一把锁,防止两个任务同时改同一份记录)和 Arc(可共享引用,让多个地方安全拿到同一份记录),只是为了在测试里保存调用痕迹。

函数细节2
noop_response_item_injector_returns_original_items14–29 ↗
async fn noop_response_item_injector_returns_original_items()

作用:这个测试确认“空操作回复注入器”不会擅自改输入内容。它故意把一条用户消息交给注入器,然后要求它拒绝同轮注入,并把原消息退回来。

数据流:进去的是一组对话输入项,里面有一条用户文字“keep this input” → 测试调用 NoopResponseItemInjector.inject_response_items,把这组输入交给它 → 出来的是一个错误结果,但错误里带回了原始输入;测试再比较带回来的内容和一开始的内容是否完全一样,没有改动外部状态。

调用关系:这个测试直接针对 NoopResponseItemInjector 的默认行为。它没有把工作再交给别的项目函数,只用断言来检查结果:当扩展系统选择“不注入内容”这条路时,调用方可以相信原始输入不会被吞掉或篡改。

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

closure_agent_spawner_forwards_arguments_and_result32–56 ↗
async fn closure_agent_spawner_forwards_arguments_and_result()

作用:这个测试确认一个普通闭包也能被当作 AgentSpawner 使用,也就是能启动子代理的东西。它重点检查两件事:参数有没有原样传进去,异步返回值有没有原样传出来。

数据流:进去的是一个线程编号和请求文本“delegate this” → 测试闭包先把这两个参数记录到共享列表里,再异步返回请求文本的长度 → 出来的是 Ok(13),同时共享列表里多了一条调用记录,内容正好是刚才的线程编号和请求文本。

调用关系:这个测试站在扩展调用方的角度使用 spawner.spawn_subagent。实际工作由测试里写的闭包完成:spawn_subagent 把线程编号和请求交给闭包,闭包记录调用并返回结果;最后测试用断言确认这条转交链路没有丢参数、改参数或改结果。

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

ext/extension-api/tests/state.rs源码 ↗
testtest run

ExtensionData 可以理解成扩展系统里的一只“分类储物箱”:同一个箱子里可以放 u64、String 这类不同类型的东西,取的时候按类型取。这个测试文件就是检查这只箱子靠不靠谱。它先验证放入、替换、删除都符合预期:同类型的新值会替掉旧值,不同类型互不干扰。然后它模拟 8 个线程同时来要同一个还不存在的值,确认初始化函数只会真正跑一次,所有人拿到的是同一份共享结果。它还检查两个看起来名字一样的仓库其实彼此隔离,不会拿到对方的数据。最后,它故意让初始化函数崩溃,确认这次失败不会把仓库弄坏,之后还能正常初始化并取值。

函数细节4
typed_values_can_be_inserted_replaced_and_removed10–30 ↗
fn typed_values_can_be_inserted_replaced_and_removed()

作用:这个测试确认 ExtensionData 能按“数据类型”存东西、换东西、删东西。它证明同一个仓库里放数字和字符串时,彼此不会混在一起。

数据流:进去的是一个新建的 ExtensionData 仓库,名字是 thread-1。测试先放入一个 u64 数字和一个 String 字符串,再按类型把它们取出来检查;接着放入新的 u64,确认旧的 u64 被返回并被替换;最后删除 String,确认字符串没了,但 u64 还在。出来的结果不是业务数据,而是一连串断言通过,说明这个仓库的基本增删改查行为正确。

调用关系:这个测试从 ExtensionData::new 建出一个空仓库,然后围绕 insert、get、remove 这些核心动作做检查。它用 assert_eq! 把每一步的实际结果和预期结果对上,属于最基础的行为验收,后面的并发和异常测试都建立在这个基本能力可信的前提上。

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

get_or_init_initializes_once_and_returns_shared_value33–76 ↗
fn get_or_init_initializes_once_and_returns_shared_value()

作用:这个测试确认很多线程同时要同一个值时,ExtensionData 不会重复初始化。换句话说,大家一起抢着开同一个保险箱,真正配钥匙的人只能有一个,其他人都拿同一把已经配好的钥匙。

数据流:进去的是一个被 Arc 包起来的 ExtensionData,Arc 是一种“多人共享同一份东西”的安全指针;还进去两个 AtomicUsize 计数器,AtomicUsize 是线程之间安全计数用的数字。测试启动 8 个线程,每个线程都调用 get_or_init。第一个真正进入初始化的人会增加初始化计数,并等待其他线程也到场,以制造同时争抢的场景。最后收集所有线程返回的 Arc<SharedValue>,检查初始化次数等于 1,并且 8 个线程拿到的是同一个共享对象,而不是 8 份长得一样的副本。

调用关系:这个测试先用 ExtensionData::new 创建共享仓库,又用标准库的 from_fn 批量创建线程任务。线程内部把真正的初始化工作交给 get_or_init,主线程等待所有线程结束后,用 assert_eq! 和 assert! 检查结果。它验证的是 ExtensionData 在并发场景下最关键的承诺:缺值时只初始化一次,后续调用共享同一份值。

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

stores_are_isolated_and_preserve_level_id79–97 ↗
fn stores_are_isolated_and_preserve_level_id()

作用:这个测试确认两个 ExtensionData 仓库即使使用同一个 level_id,也仍然是两只独立的箱子。它还检查创建时给仓库的 level_id 会被原样保存下来。

数据流:进去的是两个新建的 ExtensionData,二者都叫 root-1。测试往第一个仓库放入 u32 数字,往第二个仓库放入字符串。然后分别读取 level_id,确认都是 root-1;再交叉取值,确认第一个仓库只能拿到自己的数字、拿不到第二个仓库的字符串,第二个仓库也拿不到第一个仓库的数字。出来的结果是断言通过,说明名字相同不代表数据共享。

调用关系:这个测试通过 ExtensionData::new 创建两个独立实例,再用 assert_eq! 检查它们的身份信息和内部数据。它补上了一个容易误解的点:level_id 是记录这个仓库属于哪个层级的标签,不是把多个仓库合并到一起的钥匙。

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

store_remains_usable_after_panicking_initializer100–109 ↗
fn store_remains_usable_after_panicking_initializer()

作用:这个测试确认 get_or_init 的初始化函数如果崩溃了,ExtensionData 不会进入坏状态。也就是说,一次失败的开箱操作不该把整只箱子锁死。

数据流:进去的是一个新建的 ExtensionData,名字是 turn-1。测试先用 catch_unwind 包住一次会 panic 的 get_or_init 调用;panic 是 Rust 里的“程序遇到严重错误并中断当前执行”的机制,catch_unwind 则是把这次中断接住,避免测试整个崩掉。确认这次确实失败后,测试再次调用 get_or_init,这次正常返回 99_u64,并检查取到的值就是 99。出来的结果是仓库在失败后仍可继续初始化和读取。

调用关系:这个测试先用 AssertUnwindSafe 和 catch_unwind 搭出一个安全捕获崩溃的外壳,再把会失败的初始化交给 get_or_init。失败被确认后,它马上再次使用同一个仓库做正常初始化,并用 assert!、assert_eq! 检查状态恢复正常。它验证的是异常路径:即使初始化半路炸了,ExtensionData 后续仍然可靠。

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

ext/extension-api/tests/registry.rs源码 ↗
testtest run

这个测试文件专门检查扩展 API 的“登记处”是否可靠。可以把 ExtensionRegistryBuilder 理解成一个报名表:各种贡献者,也就是插件提供的小功能,先被登记进去;build 之后变成 registry,也就是正式名单。测试会确认每一类贡献者都能从报名表走到正式名单里;多个贡献者会不会保持先来后到;审批检查是不是谁先给出明确答案就采用谁,并且不再问后面的人;自定义事件接收器会不会在 build 之后还活着;空注册表会不会老老实实表示“没人处理”。文件里有一些假的贡献者和记录器,它们不做真实业务,只负责返回固定结果或把调用记录下来,方便测试确认实际发生了什么。

函数细节12
AllContributors::tools71–77 ↗
fn tools(
        &self,
        _session_store: &ExtensionData,
        _thread_store: &ExtensionData,
    ) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>

作用:这是一个测试用的工具贡献函数,表示这个贡献者支持“工具列表”这类扩展点,但实际不提供任何工具。有人用它,是为了证明注册表能接住工具贡献者这个类别,而不是为了测试具体工具执行。

数据流:进去的是会话级和线程级的扩展数据,但这个测试函数故意不看这些数据。它只是创建一个空列表。出来的是一个空的工具执行器列表,外部状态没有被改动。

调用关系:它属于 AllContributors 这个“全能假插件”的一部分。在 build_round_trips_every_contributor_category 里,这个对象会被登记成工具贡献者;测试随后只检查注册表里确实有这个类别的贡献者。

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

AllContributors::contribute97–107 ↗
fn contribute(
        &'a self,
        _session_store: &'a ExtensionData,
        _thread_store: &'a ExtensionData,
        _prompt: &'a str,
    ) -> ExtensionFuture<'a, Option<ReviewDecision>>

作用:这里的 contribute 是 AllContributors 作为“审批审查贡献者”时的回答函数。它总是说“本会话批准”,用来测试审批贡献者能不能被注册表调用到。

数据流:进去的是会话数据、线程数据和一段要审查的提示文字。函数没有真正分析这些内容,只是包装出一个异步结果。出来的是 Some(ApprovedForSession),意思是给出明确审批结论,并且结论是本会话内批准。

调用关系:build_round_trips_every_contributor_category 把 AllContributors 登记到审批审查类别里,然后通过 registry.approval_review 调到它。这个函数不再把活儿交给别的函数,只返回固定答案,方便测试注册表通路是否打通。

调用图:外部调用 3 个(pin, new, ready)。

build_round_trips_every_contributor_category111–145 ↗
async fn build_round_trips_every_contributor_category()

作用:这个测试确认:注册表构建器登记的每一种扩展贡献者,build 之后都还在,而且审批审查也能真正跑起来。它防止某个扩展类别在构建过程中被漏掉。

数据流:开始时创建一个 AllContributors,并把它按十来种身份登记进 ExtensionRegistryBuilder。然后 build 出 registry,再逐个读取 registry 里的各类贡献者数量。最后调用一次审批审查。结果应该是每类数量都是 1,审批结果是 ApprovedForSession。

调用关系:这是覆盖面最广的注册表测试。它使用 AllContributors::tools 和 AllContributors::contribute 等假实现来模拟所有扩展类别,重点不是这些贡献者做什么,而是确认 builder 到 registry 的“搬运过程”没有丢件。

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

NamedContextContributor::contribute150–158 ↗
fn contribute(
        &'a self,
        _session_store: &'a ExtensionData,
        _thread_store: &'a ExtensionData,
    ) -> ExtensionFuture<'a, Vec<PromptFragment>>

作用:这个函数生成一段带名字的提示片段,用来测试上下文贡献者的调用顺序。它像是在名单上写下“我是 first”或“我是 second”。

数据流:进去的是会话数据和线程数据,但函数不使用它们。它读取自己保存的名字字符串,把它包装成一个 developer_policy 类型的 PromptFragment。出来的是只含这一段提示片段的列表。

调用关系:contributors_preserve_registration_order 会创建两个 NamedContextContributor,名字分别是 first 和 second。测试逐个调用注册表里的上下文贡献者,靠这个函数的返回内容来判断顺序有没有被打乱。

调用图:外部调用 3 个(pin, ready, vec!)。

RecordingTurnItemContributor::contribute167–180 ↗
fn contribute(
        &'a self,
        _thread_store: &'a ExtensionData,
        _turn_store: &'a ExtensionData,
        _item: &'a mut TurnItem,
    ) -> ExtensionFuture<'a, Result<(), String>>

作用:这个函数不修改对话条目本身,而是把“我被调用了”的名字记到共享记录本里。它用来检查 turn item 贡献者是否按登记顺序执行。

数据流:进去的是线程数据、轮次数据和一个可修改的 TurnItem,但函数不真正改 TurnItem。它拿到自己的 name,锁住共享的 Vec 记录表,把 name 追加进去。出来的是 Ok(()),表示贡献成功,同时共享记录表多了一条调用记录。

调用关系:contributors_preserve_registration_order 会登记两个 RecordingTurnItemContributor。测试调用注册表里的 turn item 贡献者时,这个函数负责留下调用痕迹,最后测试读取记录表,确认顺序是 first 再 second。这里的 Mutex 是一把锁,防止多个任务同时改同一个记录表。

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

contributors_preserve_registration_order184–229 ↗
async fn contributors_preserve_registration_order()

作用:这个测试确认多个贡献者按什么顺序登记,就会按什么顺序被拿出来和执行。这个顺序很重要,因为前面的扩展可能会影响后面的输入或输出。

数据流:测试先建立一个共享调用记录表,再按 first、second 的顺序登记两个上下文贡献者和两个 turn item 贡献者。build 后,它手动调用上下文贡献者收集提示片段,再创建一个 HookPrompt 类型的 TurnItem,依次调用 turn item 贡献者。最后结果应该显示:提示片段和调用记录都是 first 在前、second 在后。

调用关系:它把 NamedContextContributor::contribute 的返回值和 RecordingTurnItemContributor::contribute 写下的记录放在一起验证。这个测试保护的是注册表的基本承诺:登记顺序不能偷偷变。

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

RecordingApprovalContributor::contribute246–264 ↗
fn contribute(
        &'a self,
        session_store: &'a ExtensionData,
        thread_store: &'a ExtensionData,
        prompt: &'a str,
    ) -> ExtensionFuture<'a, Option<ReviewDecision>>

作用:这个函数模拟一个审批审查插件:它会记录自己被问到了什么,然后返回预先设置好的决定。它用来测试审批流程是不是按顺序问、是不是拿到第一个明确答案就停。

数据流:进去的是会话存储、线程存储和提示文字。函数读取会话 id、线程 id、提示文字和自己的名字,锁住共享记录表,追加一条 ApprovalCall。然后返回自己配置好的 decision,可能是 None,也可能是批准或拒绝。

调用关系:approval_review_returns_first_claim_and_short_circuits 会创建三个这样的审批贡献者。注册表调用审批流程时会依次进入这个函数;测试通过记录表确认前两个被调用了,第三个没有被调用,因为第二个已经给出明确决定。

调用图:调用 1 个内部函数(level_id);外部调用 1 个(pin)。

approval_review_returns_first_claim_and_short_circuits268–310 ↗
async fn approval_review_returns_first_claim_and_short_circuits()

作用:这个测试确认审批审查的规则:按登记顺序询问贡献者;如果某个贡献者给出明确决定,就采用它,并且不再问后面的贡献者。这样可以避免后面的插件覆盖前面已经认领的判断。

数据流:测试创建三个审批贡献者:第一个返回 None,表示不认领;第二个返回 Approved;第三个返回 Denied。调用 registry.approval_review 后,结果应该是 Approved。共享记录表里应该只有 first 和 second 的调用记录,没有 third。

调用关系:它直接验证 RecordingApprovalContributor::contribute 被调用的顺序和停止点。这个测试保护 approval_review 这个总入口的行为:找到第一个有答案的人就短路,所谓短路就是提前结束,不继续往下跑。

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

RecordingEventSink::emit318–326 ↗
fn emit(&self, event: Event)

作用:这个函数是测试用的事件接收器,只接受警告事件,并把事件 id 和警告文字记下来。它用来确认自定义事件接收器在构建注册表前后都是同一个。

数据流:进去的是一个 Event。函数先检查它是不是 Warning 类型;如果不是,就直接让测试失败。若是警告,就锁住内部记录表,把事件 id 和 warning.message 作为一对字符串存进去。出来没有返回值,但记录表发生了变化。

调用关系:custom_event_sink_survives_registry_build 会通过 builder.event_sink 和 registry.event_sink 分别调用它。它不把工作交给别的项目函数,只承担“收集证据”的角色。

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

custom_event_sink_survives_registry_build330–352 ↗
fn custom_event_sink_survives_registry_build()

作用:这个测试确认:如果用户给注册表构建器指定了自己的事件接收器,build 之后注册表仍然使用同一个接收器。否则,构建前后发出的事件可能会跑到不同地方,调试和日志都会乱。

数据流:测试先创建一个 RecordingEventSink,再用 with_event_sink 放进 builder。build 前通过 builder 发一个 warning 事件,build 后通过 registry 再发一个 warning 事件。最后读取同一个 sink 的记录表,应该看到 before 和 after 两条记录都在里面。

调用关系:它调用 warning_event 来造测试事件,并依赖 RecordingEventSink::emit 记录事件。这个测试串起了 builder、registry 和事件接收器,确认事件通道没有在 build 时被替换掉。

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

empty_registry_does_not_claim_approval_review355–368 ↗
async fn empty_registry_does_not_claim_approval_review()

作用:这个测试确认空注册表不会凭空给出审批决定。没有插件认领审批时,结果必须是 None。

数据流:测试先创建一个 empty_extension_registry,也就是没有登记任何贡献者的注册表。然后调用 approval_review,传入会话数据、线程数据和提示文字。出来的结果应该是 None,表示没人处理、没人批准、也没人拒绝。

调用关系:它检查审批流程的空场景,是 approval_review_returns_first_claim_and_short_circuits 的补充。前者测试有人时如何短路,这里测试没人时不要乱给答案。

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

warning_event370–377 ↗
fn warning_event(id: &str, message: &str) -> Event

作用:这个小工具函数用来快速造一个警告事件,避免测试里重复写一大段结构体创建代码。它让事件测试更短、更清楚。

数据流:进去的是事件 id 和警告消息两个字符串切片。函数把它们复制成 String,放进 Event,其中 msg 是 WarningEvent。出来的是一个完整的 Event,可以直接交给事件接收器。

调用关系:custom_event_sink_survives_registry_build 在 build 前后各调用一次 warning_event,生成两条警告事件。它只是测试辅助函数,不参与注册表核心逻辑。

调用图:被 1 处调用(custom_event_sink_survives_registry_build);外部调用 1 个(Warning)。

ext/goal/tests/goal_extension_backend.rs源码 ↗
testtest execution

可以把“目标扩展”理解成给一次对话挂一个任务牌:目标是什么、还剩多少预算、用了多少 token、现在是进行中还是受阻。这个测试文件搭了一个假的运行环境,装上目标扩展,然后像真实系统一样触发线程开始、回合开始、工具结束、报错、线程恢复等事件。它检查工具什么时候应该出现,目标能不能被重复创建,token 是否只算一次,预算或使用限制触发后状态是否正确,还会记录系统发出的目标更新事件。这里的 GoalExtensionHarness 像一个小型试验台,RecordingEventSink 像录音笔,帮测试看清扩展对外发了什么消息。

函数细节46
installed_goal_tools_create_goal_and_fill_empty_preview51–95 ↗
async fn installed_goal_tools_create_goal_and_fill_empty_preview() -> anyhow::Result<()>

作用:测试安装目标工具后,调用 create_goal 能真正创建目标。还检查如果线程预览为空,目标文字会被填进去,方便列表里显示这次对话在做什么。

数据流:先创建临时运行环境和固定线程编号,再写入线程元数据,装好工具。它构造一个 create_goal 调用,传入目标和 token 预算,然后检查返回的 JSON、数据库里的目标数据,以及线程 preview 是否变成目标文字。

调用关系:这是最基础的端到端测试。它依赖 test_runtime、test_thread_id、seed_thread_metadata 准备环境,通过 installed_tools 拿到工具,再用 tool_by_name 和 tool_call 模拟模型调用工具。

调用图:调用 6 个内部函数(installed_tools, seed_thread_metadata, test_runtime, test_thread_id, tool_by_name, tool_call);外部调用 2 个(assert_eq!, json!)。

goal_tools_hidden_for_ephemeral_threads98–111 ↗
async fn goal_tools_hidden_for_ephemeral_threads() -> anyhow::Result<()>

作用:测试临时线程里不应该显示目标工具。因为临时线程没有可靠的持久状态,给它设置长期目标会丢数据或误导用户。

数据流:它创建运行环境和线程编号,然后用 installed_tools_with_start 模拟一个没有持久线程状态的启动。最后读取工具名列表,确认是空的。

调用关系:这个测试直接验证扩展安装阶段的过滤规则。它把特殊启动参数交给 installed_tools_with_start,再用 tool_names 看最终有没有工具暴露出来。

调用图:调用 3 个内部函数(installed_tools_with_start, test_runtime, test_thread_id);外部调用 1 个(assert_eq!)。

goal_tools_hidden_for_review_subagents114–127 ↗
async fn goal_tools_hidden_for_review_subagents() -> anyhow::Result<()>

作用:测试评审子代理里不应该显示目标工具。评审子代理是辅助角色,不该修改主线程的目标状态。

数据流:它创建测试运行环境和线程编号,模拟 SessionSource::SubAgent(SubAgentSource::Review) 这种评审子代理启动。之后收集工具名,确认没有任何目标工具。

调用关系:它和临时线程测试类似,都是检查“什么时候不要装工具”。它调用 installed_tools_with_start 设置会话来源,再用 tool_names 做最终判断。

调用图:调用 3 个内部函数(installed_tools_with_start, test_runtime, test_thread_id);外部调用 2 个(SubAgent, assert_eq!)。

installed_goal_tools_only_replace_complete_goal130–184 ↗
async fn installed_goal_tools_only_replace_complete_goal() -> anyhow::Result<()>

作用:测试未完成的目标不能被新目标直接覆盖。只有旧目标完成后,才允许创建替代目标,避免任务被无意中冲掉。

数据流:它先创建第一个目标,再尝试创建第二个目标,期望得到明确错误。接着用 update_goal 把旧目标标为 complete,最后再次调用 create_goal,确认新目标能创建且状态重新变为 active。

调用关系:这个测试通过 GoalExtensionHarness::new 搭建完整试验台。它连续使用 create_goal 和 update_goal 两个工具,检查目标扩展内部的防覆盖规则。

调用图:调用 6 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, panic!)。

create_goal_resets_baseline_before_turn_stop_accounting187–243 ↗
async fn create_goal_resets_baseline_before_turn_stop_accounting() -> anyhow::Result<()>

作用:测试在一个回合中途创建目标时,token 统计会从创建那一刻重新算起。这样目标不会把创建之前消耗的 token 也算到自己头上。

数据流:它先启动回合并记录已有 token,再创建目标。之后继续记录新的 token 并停止回合,最后读取目标,确认只累计创建目标之后新增的 15 个 token。

调用关系:它用 GoalExtensionHarness 模拟回合开始、token 上报、工具调用和回合结束。token_usage 负责造出不同时间点的用量快照。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 2 个(assert_eq!, json!)。

tool_finish_accounts_active_goal_progress_and_emits_event246–294 ↗
async fn tool_finish_accounts_active_goal_progress_and_emits_event() -> anyhow::Result<()>

作用:测试工具执行结束时,活跃目标会结算当前进度,并发出目标更新事件。这样界面或外部监听者能及时看到目标用了多少 token。

数据流:它创建目标后清空事件记录,再上报 token 用量并通知某个 shell 工具结束。最后读取目标确认 tokens_used 增加到 23,并确认事件记录里有一条对应更新。

调用关系:这个测试把工具结束当作结算点。notify_tool_finish 触发生命周期钩子,RecordingEventSink 负责捕获扩展发出的事件。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

parallel_tool_finish_accounts_active_goal_progress_once297–357 ↗
async fn parallel_tool_finish_accounts_active_goal_progress_once() -> anyhow::Result<()>

作用:测试两个工具几乎同时结束时,目标进度不会被重复计算。它防止并发场景下 token 被算两遍。

数据流:它启动回合、创建目标、上报 token 从 100 增到 130,然后并行通知两个工具结束。最后确认目标只增加 30 个 token,事件也只记录一次。

调用关系:这个测试用 tokio::join! 模拟并发。两个 notify_tool_finish 同时进入扩展,目标后端必须靠内部同步保证只结算一次。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, join!)。

budget_limited_goal_keeps_accruing_until_turn_stop360–433 ↗
async fn budget_limited_goal_keeps_accruing_until_turn_stop() -> anyhow::Result<()>

作用:测试目标超过 token 预算后,状态会变成预算受限,但仍会继续统计到回合结束。这样最终账本不会停在预算线那里。

数据流:它创建一个预算为 25 的目标,先上报 25 个有效 token 并触发工具结束,再继续增加用量直到回合停止。最后确认目标累计到 35,并发出工具结束和回合结束两次事件。

调用关系:它串起 create_goal、record_token_usage、notify_tool_finish 和 stop_turn。重点验证预算状态变化后,回合结束钩子仍会补齐剩余用量。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

budget_limited_goal_keeps_accounting_after_later_tool_finish436–491 ↗
async fn budget_limited_goal_keeps_accounting_after_later_tool_finish() -> anyhow::Result<()>

作用:测试目标已经预算受限后,后续工具结束仍会继续结算新增 token。它保证超预算不是“停止记账”。

数据流:它创建预算目标,第一次工具结束让目标达到预算受限。然后继续上报更多 token,再通知第二个工具结束,最后确认总 token 变成 35,状态仍是 BudgetLimited。

调用关系:它和上一条测试互补:一个看回合停止,一个看后续工具结束。都通过 GoalExtensionHarness 模拟真实生命周期事件。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

turn_error_usage_limit_accounts_progress_and_clears_accounting494–573 ↗
async fn turn_error_usage_limit_accounts_progress_and_clears_accounting() -> anyhow::Result<()>

作用:测试遇到使用额度限制错误时,会先把当前目标进度结算完,再把目标标成 UsageLimited。之后同一回合再来的 token 不应继续算进去。

数据流:它创建目标、上报 23 个有效 token,然后发送 UsageLimitExceeded 错误。目标先记录 23,再变成 UsageLimited;随后即使继续上报 token、工具结束、回合结束,目标仍保持 23。

调用关系:它通过 notify_turn_error 触发错误处理路径。RecordingEventSink 用来确认系统先发进度事件,再发使用受限事件。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

turn_error_blocks_goal576–603 ↗
async fn turn_error_blocks_goal() -> anyhow::Result<()>

作用:测试普通错误会把目标标记为 blocked,也就是“被阻塞”。这表示任务不是完成了,而是因为问题暂时走不下去。

数据流:它创建目标后,向回合生命周期发送 CodexErrorInfo::Other。然后读取目标,确认状态从 active 变成 Blocked。

调用关系:它使用 GoalExtensionHarness 建环境,用 tool_by_name 和 tool_call 创建目标,再由 notify_turn_error 进入错误处理分支。

调用图:调用 6 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

usage_limit_budget_limited_goal_accounts_remaining_progress606–682 ↗
async fn usage_limit_budget_limited_goal_accounts_remaining_progress() -> anyhow::Result<()>

作用:测试目标已经预算受限时,如果又遇到使用额度限制,也要先补算剩余进度,再改成 UsageLimited。

数据流:它先让预算为 25 的目标达到 BudgetLimited,再增加 token 到 35。随后直接调用运行时句柄的 usage_limit_active_goal_for_turn,最后确认目标 tokens_used 是 35,状态是 UsageLimited。

调用关系:这个测试绕过普通错误通知,直接使用 GoalRuntimeHandle 的接口。它验证服务内部提供给外部调用的使用限制处理也正确。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

usage_limit_plan_turn_does_not_stop_goal685–719 ↗
async fn usage_limit_plan_turn_does_not_stop_goal() -> anyhow::Result<()>

作用:测试计划模式回合里的使用限制不会停止当前目标。计划模式只是规划,不应把执行目标标成失败或受限。

数据流:它先创建目标,再用 start_turn_with_mode 启动一个 ModeKind::Plan 回合。调用 usage_limit_active_goal_for_turn 后,确认目标仍是 Active,且没有目标更新事件。

调用关系:它用 GoalExtensionHarness::start_turn_with_mode 专门设置回合模式。然后直接调用 runtime_handle,验证目标运行时会识别并忽略计划回合。

调用图:调用 6 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

usage_limit_stale_turn_does_not_stop_current_goal722–756 ↗
async fn usage_limit_stale_turn_does_not_stop_current_goal() -> anyhow::Result<()>

作用:测试旧回合的使用限制不会影响当前正在进行的目标。这样延迟到达的错误事件不会误伤新回合。

数据流:它启动 turn-1、创建目标、停止 turn-1,再启动 turn-2。随后对 turn-1 调用 usage_limit_active_goal_for_turn,最后确认目标仍然 Active,事件列表为空。

调用关系:它模拟“过期事件晚到”的情况。stop_turn 和 start_turn 建立当前回合状态,runtime_handle 的调用必须判断 turn_id 是否还有效。

调用图:调用 6 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

update_goal_can_block_and_accounts_final_progress759–838 ↗
async fn update_goal_can_block_and_accounts_final_progress() -> anyhow::Result<()>

作用:测试用 update_goal 把目标改成 blocked 时,会先结算最后一段 token。这样目标结束状态和用量都准确。

数据流:它创建目标、上报 23 个有效 token,然后调用 update_goal 设置 status 为 blocked。返回结果和数据库都应显示 blocked 且 tokensUsed 为 23,并且事件里先有进度更新,再有状态更新。

调用关系:它验证 update_goal 工具不仅改状态,还会触发目标记账。tool_by_name 找到 update_goal,RecordingEventSink 检查对外事件顺序。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

external_goal_mutation_start_accounts_active_goal_progress841–890 ↗
async fn external_goal_mutation_start_accounts_active_goal_progress() -> anyhow::Result<()>

作用:测试外部准备修改目标前,会先把当前活跃目标的进度结算掉。这样外部改目标时不会丢掉刚刚消耗的 token。

数据流:它创建目标后上报 token,然后调用 runtime_handle().prepare_external_goal_mutation。最后读取目标,确认 tokens_used 已经是 23,并看到一条 external-goal-mutation 事件。

调用关系:这个测试覆盖工具以外的修改入口。GoalRuntimeHandle 提供给外部服务调用,RecordingEventSink 证明它也会发目标更新事件。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, json!, default)。

goal_service_external_set_active_resets_baseline_without_live_thread893–966 ↗
async fn goal_service_external_set_active_resets_baseline_without_live_thread() -> anyhow::Result<()>

作用:测试外部服务把目标重新设为 active 时,会重置记账基准。否则新目标可能会把旧目标期间的 token 一起算进去。

数据流:它先创建旧目标并积累到 120 的用量快照,然后通过 GoalService::set_thread_goal 改成新目标并应用运行时效果。之后用量到 130,工具结束,最终目标 tokens_used 是 30,说明基准被正确调整。

调用关系:它同时使用工具路径和 GoalService 外部 API。outcome.apply_runtime_effects 把服务层变化同步给运行时记账组件。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 3 个(assert_eq!, Set, json!)。

thread_stop_unregisters_goal_runtime_from_service969–1006 ↗
async fn thread_stop_unregisters_goal_runtime_from_service() -> anyhow::Result<()>

作用:测试线程停止时,目标运行时会从服务里注销。这样线程结束后,服务不会再误以为这个线程还活着。

数据流:它创建目标、上报一些 token,然后调用 stop_thread。之后直接用 GoalService 清除目标,确认能清除且没有额外事件被发出。

调用关系:这个测试通过 GoalExtensionHarness::stop_thread 触发线程停止钩子。它检查目标服务和线程运行时之间的登记关系会被干净拆掉。

调用图:调用 7 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id, token_usage, tool_by_name, tool_call);外部调用 4 个(assert!, assert_eq!, json!, default)。

thread_resume_rehydrates_active_goal_idle_accounting1009–1052 ↗
async fn thread_resume_rehydrates_active_goal_idle_accounting() -> anyhow::Result<()>

作用:测试线程恢复时,如果已有活跃目标,运行时会重新接上它,并继续统计空闲经过的真实时间。这里的“真实时间”就是墙上时钟走过的秒数。

数据流:它先直接在状态库里放入一个 active 目标,再创建 harness 并调用 resume_thread。等待约 1.1 秒后触发外部修改准备,最后确认目标仍 active,time_used_seconds 至少增加 1。

调用关系:它验证恢复线程路径。resume_thread 触发生命周期贡献者重建 GoalRuntimeHandle,prepare_external_goal_mutation 再迫使运行时把空闲时间写回状态。

调用图:调用 4 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id);外部调用 4 个(from_millis, assert!, assert_eq!, sleep)。

goal_service_sets_gets_and_clears_thread_goal1055–1094 ↗
async fn goal_service_sets_gets_and_clears_thread_goal() -> anyhow::Result<()>

作用:测试 GoalService 这个对外服务能设置、读取、清除线程目标。它像目标功能的公共柜台,外部代码通过它操作目标。

数据流:它创建运行环境和线程元数据,调用 set_thread_goal 写入目标,再 get_thread_goal 读回,检查文字被去掉首尾空格、状态是 Active、预算正确、线程 preview 被更新。最后 clear_thread_goal 清掉目标,并确认再次清除会返回 false。

调用关系:这是服务层 API 的直接测试,不通过工具调用。它验证 GoalService 和 codex_state 状态存储、线程元数据之间的配合。

调用图:调用 4 个内部函数(new, seed_thread_metadata, test_runtime, test_thread_id);外部调用 4 个(assert!, assert_eq!, Set, Set)。

installed_tools1096–1107 ↗
async fn installed_tools(
    runtime: Arc<codex_state::StateRuntime>,
    thread_id: ThreadId,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>

作用:这是一个测试辅助函数,用默认条件安装目标工具。测试不想每次都写一堆启动参数时,就用它拿到可用工具列表。

数据流:输入是运行环境和线程编号。它固定使用 CLI 会话、持久线程状态可用,然后转交给 installed_tools_with_start,返回安装后的工具执行器列表。

调用关系:它是 installed_tools_with_start 的简化包装。installed_goal_tools_create_goal_and_fill_empty_preview 用它走最普通的安装流程。

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

installed_tools_with_start1109–1147 ↗
async fn installed_tools_with_start(
    runtime: Arc<codex_state::StateRuntime>,
    thread_id: ThreadId,
    session_source: SessionSource,
    persistent_thread_state_available: bool,
) -> Vec<Arc<

作用:这个辅助函数按指定启动条件安装目标扩展,并返回最终暴露出来的工具。它用来测试不同线程类型下工具该不该出现。

数据流:它接收运行环境、线程编号、会话来源和是否有持久状态。函数创建扩展注册表、目标服务、会话数据和线程数据,触发 on_thread_start,然后收集所有工具贡献者提供的工具并返回。

调用关系:installed_tools 会调用它走默认路径;隐藏工具相关测试会直接调用它传入特殊条件。它内部把 install_with_backend 装进注册表,再让生命周期贡献者决定工具可见性。

调用图:调用 3 个内部函数(disabled, new, new);被 3 处调用(goal_tools_hidden_for_ephemeral_threads, goal_tools_hidden_for_review_subagents, installed_tools);外部调用 5 个(new, new, new, install_with_backend, to_string)。

tool_names1149–1151 ↗
fn tool_names(tools: &[Arc<dyn ToolExecutor<ToolCall>>]) -> Vec<String>

作用:把一组工具变成它们的名字列表,方便测试比较。它只关心工具叫什么,不关心工具怎么执行。

数据流:输入是一组工具执行器。函数逐个读取 tool.tool_name().name,收集成字符串数组返回,不修改任何状态。

调用关系:它主要服务于“工具是否隐藏”的测试。测试用它把复杂工具对象变成容易断言的 Vec<String>。

GoalExtensionHarness::new1162–1201 ↗
async fn new(
        runtime: Arc<codex_state::StateRuntime>,
        thread_id: ThreadId,
    ) -> anyhow::Result<Self>

作用:创建一个目标扩展测试台。它把真实扩展装进测试注册表,还接上一个会记录事件的假事件接收器。

数据流:输入是运行环境和线程编号。它创建 RecordingEventSink、ExtensionRegistryBuilder、GoalService、session_store 和 thread_store,安装目标扩展,触发线程启动,最后返回包含这些零件的 harness。

调用关系:大多数测试都从它开始。后续 tools、start_turn、record_token_usage 等方法都依赖它保存的 registry 和数据仓库。

调用图:调用 3 个内部函数(disabled, new, new);被 16 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, installed_goal_tools_only_replace_complete_goal, parallel_tool_finish_accounts_active_goal_progress_once, thread_resume_rehydrates_active_goal_idle_accounting, thread_stop_unregisters_goal_runtime_from_service, tool_finish_accounts_active_goal_progress_and_emits_event (+6 more));外部调用 7 个(clone, new, with_event_sink, new, install_with_backend, default, to_string)。

GoalExtensionHarness::tools1203–1209 ↗
fn tools(&self) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>

作用:从测试台里取出当前线程可用的工具列表。测试用它找到 create_goal 或 update_goal 来模拟模型调用。

数据流:它读取 registry 里的工具贡献者,并把 session_store、thread_store 交给它们。贡献者返回的工具被合并成一个列表输出。

调用关系:创建目标、更新目标等测试都会先调用它。它把扩展注册表里的工具提供机制包装成一行好用的测试接口。

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

GoalExtensionHarness::start_turn1211–1214 ↗
async fn start_turn(&self, turn_id: &str, usage: &TokenUsage)

作用:用默认模式启动一个测试回合。回合可以理解成模型一次思考和执行的周期。

数据流:输入是 turn_id 和回合开始时的 token 用量。它把模式固定为 Default,然后调用 start_turn_with_mode 完成真正的通知。

调用关系:这是 start_turn_with_mode 的快捷入口。多数测试不关心计划模式,就用它启动普通回合。

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

GoalExtensionHarness::start_turn_with_mode1216–1232 ↗
async fn start_turn_with_mode(&self, turn_id: &str, mode: ModeKind, usage: &TokenUsage)

作用:按指定模式启动一个测试回合,并通知所有关心回合开始的扩展组件。它可用来模拟普通执行或计划模式。

数据流:输入是回合编号、模式和起始 token 用量。它创建 turn_store,构造 CollaborationMode,然后逐个调用 turn_lifecycle_contributors 的 on_turn_start。

调用关系:start_turn 会调用它;计划模式测试也直接用它。它是目标扩展开始记账、识别当前回合的重要入口。

调用图:调用 3 个内部函数(turn_lifecycle_contributors, new, default_collaboration_mode);被 1 处调用(start_turn)。

GoalExtensionHarness::stop_turn1234–1245 ↗
async fn stop_turn(&self, turn_id: &str)

作用:模拟一个回合结束。目标扩展通常会在这里补算本回合还没结算的进度。

数据流:输入是 turn_id。它创建对应 turn_store,然后通知所有回合生命周期贡献者执行 on_turn_stop,不直接返回数据。

调用关系:预算结算、过期回合和停止记账相关测试会用它。它把“回合结束”这个事件送进扩展。

调用图:调用 2 个内部函数(turn_lifecycle_contributors, new)。

GoalExtensionHarness::record_token_usage1247–1264 ↗
async fn record_token_usage(&self, turn_id: &str, usage: &TokenUsage)

作用:模拟系统上报当前 token 用量。token 可以简单理解成模型读写文字时消耗的计量单位。

数据流:输入是 turn_id 和 TokenUsage。它包装成 TokenUsageInfo,其中 total_token_usage 是当前总量,last_token_usage 用默认值,然后通知所有 token_usage_contributors。

调用关系:所有检查 token 统计的测试都会调用它。它本身不算账,只负责把用量快照送给目标扩展。

调用图:调用 2 个内部函数(token_usage_contributors, new);外部调用 2 个(clone, default)。

GoalExtensionHarness::resume_thread1266–1275 ↗
async fn resume_thread(&self)

作用:模拟线程恢复。比如程序重启或重新打开对话后,扩展要重新接上已有目标。

数据流:它读取 harness 里的 session_store 和 thread_store,然后逐个通知 thread_lifecycle_contributors 的 on_thread_resume。

调用关系:thread_resume_rehydrates_active_goal_idle_accounting 用它验证恢复路径。恢复后,runtime_handle 应该重新存在并能继续记账。

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

GoalExtensionHarness::stop_thread1277–1286 ↗
async fn stop_thread(&self)

作用:模拟线程停止。线程结束时,扩展需要清理运行时登记和临时状态。

数据流:它把 session_store 和 thread_store 交给所有线程生命周期贡献者,调用 on_thread_stop。函数不返回数据,但可能让扩展释放内部登记。

调用关系:thread_stop_unregisters_goal_runtime_from_service 用它触发清理流程。它和 GoalService 的登记关系检查配合使用。

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

GoalExtensionHarness::notify_tool_finish1288–1305 ↗
async fn notify_tool_finish(&self, turn_id: &str, call_id: &str, tool_name: &str)

作用:模拟某个工具调用完成。目标扩展会把这当作一个适合结算进度的时间点。

数据流:输入是 turn_id、call_id 和工具名。它创建 turn_store 和 ToolName,构造 ToolFinishInput,通知所有工具生命周期贡献者,表示工具成功完成。

调用关系:大量 token 记账测试用它触发结算。它把测试里的“shell 工具结束了”变成扩展能收到的生命周期事件。

调用图:调用 3 个内部函数(tool_lifecycle_contributors, new, plain)。

GoalExtensionHarness::notify_turn_error1307–1320 ↗
async fn notify_turn_error(&self, turn_id: &str, error: CodexErrorInfo)

作用:模拟一个回合发生错误。不同错误会让目标进入不同状态,比如使用受限或阻塞。

数据流:输入是 turn_id 和错误信息。它创建 turn_store,克隆错误对象,然后通知所有回合生命周期贡献者的 on_turn_error。

调用关系:使用限制和普通错误测试都通过它进入错误处理流程。目标扩展接到这个事件后会决定是否结算进度、改变状态和发事件。

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

GoalExtensionHarness::runtime_handle1322–1326 ↗
fn runtime_handle(&self) -> Arc<GoalRuntimeHandle>

作用:从线程数据里取出 GoalRuntimeHandle,也就是目标运行时句柄。测试用它直接调用一些不是工具触发的内部能力。

数据流:它读取 thread_store 中保存的 GoalRuntimeHandle。如果不存在就让测试失败;存在则返回一个 Arc 包装的共享句柄。

调用关系:外部目标修改、使用限制、恢复线程等测试会调用它。它让测试绕过工具层,直接检查运行时接口。

tool_by_name1329–1337 ↗
fn tool_by_name(
    tools: &'a [Arc<dyn ToolExecutor<ToolCall>>],
    name: &str,
) -> &'a Arc<dyn ToolExecutor<ToolCall>>

作用:在工具列表里按名字找出一个工具。测试用它快速拿到 create_goal 或 update_goal。

数据流:输入是工具列表和目标名字。它逐个检查工具是否没有命名空间且名字匹配,找到就返回引用,找不到就让测试失败。

调用关系:几乎所有工具调用测试都依赖它。它把“从一堆工具里找目标工具”的重复代码集中起来。

调用图:被 16 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, installed_goal_tools_create_goal_and_fill_empty_preview, installed_goal_tools_only_replace_complete_goal, parallel_tool_finish_accounts_active_goal_progress_once, thread_stop_unregisters_goal_runtime_from_service, tool_finish_accounts_active_goal_progress_and_emits_event (+6 more))。

tool_call1339–1353 ↗
fn tool_call(tool_name: &str, call_id: &str, arguments: serde_json::Value) -> ToolCall

作用:构造一个假的工具调用请求。它让测试可以像模型真的调用工具一样,把参数传给工具执行器。

数据流:输入是工具名、调用编号和 JSON 参数。它生成 ToolCall,包含固定 turn_id、模型名、截断策略、空对话历史、无操作输出器,以及字符串化后的函数参数。

调用关系:create_goal 和 update_goal 的测试都用它造调用对象。tool_by_name 找到工具后,测试会把这个 ToolCall 交给 handle 执行。

调用图:调用 1 个内部函数(plain);被 16 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, installed_goal_tools_create_goal_and_fill_empty_preview, installed_goal_tools_only_replace_complete_goal, parallel_tool_finish_accounts_active_goal_progress_once, thread_stop_unregisters_goal_runtime_from_service, tool_finish_accounts_active_goal_progress_and_emits_event (+6 more));外部调用 5 个(new, to_string, new, Bytes, default)。

test_runtime1355–1358 ↗
async fn test_runtime() -> anyhow::Result<Arc<codex_state::StateRuntime>>

作用:创建一个临时的状态运行环境。测试用它避免污染真实用户数据。

数据流:它先创建临时目录,再调用 codex_state::StateRuntime::init,用 test-provider 初始化状态库,最后返回共享的 StateRuntime。

调用关系:几乎每个测试都从它开始。seed_thread_metadata、GoalService 和目标扩展都会使用这个临时运行环境读写状态。

调用图:调用 1 个内部函数(init);被 20 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, goal_service_sets_gets_and_clears_thread_goal, goal_tools_hidden_for_ephemeral_threads, goal_tools_hidden_for_review_subagents, installed_goal_tools_create_goal_and_fill_empty_preview, installed_goal_tools_only_replace_complete_goal (+10 more));外部调用 1 个(new)。

test_thread_id1360–1362 ↗
fn test_thread_id() -> anyhow::Result<ThreadId>

作用:返回一个固定的测试线程编号。固定编号让断言里的结果稳定、可预测。

数据流:它把写死的 UUID 字符串解析成 ThreadId。解析失败时转换成 anyhow 错误返回。

调用关系:所有需要线程的测试都用它。后续 seed_thread_metadata、工具安装和目标读写都围绕这个 thread_id 进行。

调用图:调用 1 个内部函数(from_string);被 20 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, goal_service_sets_gets_and_clears_thread_goal, goal_tools_hidden_for_ephemeral_threads, goal_tools_hidden_for_review_subagents, installed_goal_tools_create_goal_and_fill_empty_preview, installed_goal_tools_only_replace_complete_goal (+10 more))。

seed_thread_metadata1364–1377 ↗
async fn seed_thread_metadata(
    runtime: &codex_state::StateRuntime,
    thread_id: ThreadId,
) -> anyhow::Result<()>

作用:往测试状态库里预先写入线程元数据。没有这一步,目标服务可能找不到要更新 preview 的线程。

数据流:输入是运行环境和线程编号。它构造 ThreadMetadataBuilder,设置 rollout 文件路径、当前时间和 CLI 来源,然后 upsert 到状态库。

调用关系:多数测试在创建目标前调用它。它给 GoalService 和目标工具准备一个真实存在的线程记录。

调用图:调用 2 个内部函数(new, codex_home);被 18 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, goal_service_sets_gets_and_clears_thread_goal, installed_goal_tools_create_goal_and_fill_empty_preview, installed_goal_tools_only_replace_complete_goal, parallel_tool_finish_accounts_active_goal_progress_once, thread_resume_rehydrates_active_goal_idle_accounting (+8 more));外部调用 3 个(now, format!, upsert_thread)。

RecordingEventSink::goal_events1385–1398 ↗
fn goal_events(&self) -> Vec<CapturedGoalEvent>

作用:从记录下来的所有事件里筛出目标更新事件。测试用它只看关心的目标状态和 token 数。

数据流:它先通过 events 拿到事件列表,再逐个匹配 EventMsg::ThreadGoalUpdated。匹配到的事件被压缩成 CapturedGoalEvent 列表返回。

调用关系:检查事件输出的测试会调用它。它依赖 RecordingEventSink::events 读取内部 Vec<Event>。

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

RecordingEventSink::clear1400–1402 ↗
fn clear(&self)

作用:清空已经记录的事件。测试常在创建目标后清空,避免前面的事件干扰后面的断言。

数据流:它通过 events 拿到事件列表的可变锁,然后调用 clear 删除所有记录。不返回内容,只改变 RecordingEventSink 内部状态。

调用关系:很多测试在进入真正要观察的步骤前调用它。它和 goal_events 配合,让断言只针对某一段行为。

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

RecordingEventSink::events1404–1406 ↗
fn events(&self) -> std::sync::MutexGuard<'_, Vec<Event>>

作用:安全地拿到内部事件列表。这里用互斥锁,也就是一把锁,防止多个异步任务同时改同一个 Vec。

数据流:它尝试锁住 events 这个 Mutex。如果锁曾因 panic 中毒,就用 PoisonError::into_inner 继续取回数据,返回 MutexGuard。

调用关系:clear、goal_events 和 emit 都通过它访问事件列表。它是 RecordingEventSink 内部读写事件的唯一小门。

调用图:被 3 处调用(clear, emit, goal_events)。

RecordingEventSink::emit1410–1412 ↗
fn emit(&self, event: Event)

作用:实现扩展事件接收器接口,把扩展发出的事件记下来。它像测试里的录音笔,只收集不处理。

数据流:输入是一个 Event。它通过 events 拿到内部列表,然后把事件 push 进去,不返回结果。

调用关系:扩展注册表在运行时发事件时会调用它。之后测试通过 goal_events 查看这些被记录的事件。

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

default_collaboration_mode1423–1432 ↗
fn default_collaboration_mode() -> CollaborationMode

作用:生成一个默认协作模式配置。测试启动普通回合时需要一份看起来像真实系统的模式信息。

数据流:它不接收输入,返回 CollaborationMode:模式是 Default,设置里模型名是 gpt-5,推理力度和开发者指令为空。

调用关系:GoalExtensionHarness::start_turn_with_mode 会调用它,然后按需要把 mode 改成指定模式,比如 Plan。

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

token_usage1434–1448 ↗
fn token_usage(
    input_tokens: i64,
    cached_input_tokens: i64,
    output_tokens: i64,
    reasoning_output_tokens: i64,
    total_tokens: i64,
) -> TokenUsage

作用:快速构造 TokenUsage 测试数据。这样测试可以清楚写出输入 token、缓存 token、输出 token 等数字。

数据流:输入是五个 token 数字。函数把它们填进 TokenUsage 结构体并返回,不做额外计算。

调用关系:所有 token 统计相关测试都用它制造用量快照。record_token_usage 再把这些快照送进扩展。

调用图:被 11 处调用(budget_limited_goal_keeps_accounting_after_later_tool_finish, budget_limited_goal_keeps_accruing_until_turn_stop, create_goal_resets_baseline_before_turn_stop_accounting, external_goal_mutation_start_accounts_active_goal_progress, goal_service_external_set_active_resets_baseline_without_live_thread, parallel_tool_finish_accounts_active_goal_progress_once, thread_stop_unregisters_goal_runtime_from_service, tool_finish_accounts_active_goal_progress_and_emits_event, turn_error_usage_limit_accounts_progress_and_clears_accounting, update_goal_can_block_and_accounts_final_progress (+1 more))。

protocol_status1450–1459 ↗
fn protocol_status(status: codex_state::ThreadGoalStatus) -> ThreadGoalStatus

作用:把状态库里的目标状态转换成协议层的目标状态。两边名字相同,但类型不同,需要显式对应起来。

数据流:输入是 codex_state::ThreadGoalStatus。函数按每一种状态逐项匹配,输出 codex_protocol::protocol::ThreadGoalStatus。

调用关系:少数测试用它比较状态库结果和协议事件里的状态。它避免测试里反复写同样的状态转换。

ext/image-generation/src/tests.rs源码 ↗
testtest

这个文件像一份“验收清单”,专门检查图片生成扩展有没有按约定工作。它先确认工具注册时使用的是保留的图片生成命名空间和工具名,避免系统认错工具。然后检查几种用户输入:如果用户只写提示词,就应该走“生成新图”,并带上固定默认值;如果用户要求参考最近几张图,就要从对话历史里找出最新图片,并保持正确顺序;如果用户同时给文件路径和“最近几张图”,或者给太多路径,就要直接报清楚的错。最后它还检查生成图片返回给模型时的格式:图片会被包成 data URL(一种把图片字节直接写进文本里的格式),可选的保存提示太长时会被省略,避免输出撑爆。整体上,这些测试保证图片工具和外部协议之间的“接口合同”稳定可靠。

函数细节13
uses_reserved_image_gen_namespace31–38 ↗
fn uses_reserved_image_gen_namespace()

作用:这个测试确认图片生成工具对外登记时,用的是系统预留的图片生成命名空间和正确的函数名。这样别的部分才能准确找到它,不会把它当成普通工具或认错名字。

数据流:进去的是 imagegen_tool_spec 生成的工具说明 → 测试把它拆开,看它是不是命名空间工具,再检查命名空间名字和里面第一个函数名字 → 出来的是测试通过或失败;如果格式不对,会直接 panic,也就是让测试报错。

调用关系:它在测试运行时调用 imagegen_tool_spec 拿到工具广告牌,然后用 assert_eq! 对关键名字做核对;如果拿到的不是预期类型,就用 panic! 中断,提醒开发者工具注册方式变了。

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

omitted_references_generate_with_fixed_defaults41–63 ↗
async fn omitted_references_generate_with_fixed_defaults()

作用:这个异步测试检查:用户没有提供参考图片时,工具应该创建“生成新图片”的请求,而不是误以为要编辑图片。它还确认默认模型、尺寸、质量、背景这些固定值没有被改掉。

数据流:进去的是一个只有提示词、没有参考图片路径、也没有最近图片数量的 ImagegenArgs,以及空的历史记录和空的辅助输入 → 构造请求后,测试拿它和预期的 ImageGenerationRequest 对比 → 出来的是一个带默认值的 Generate 请求;如果任何字段不同,测试失败。

调用关系:它属于参数转换流程的基础保护测试:在测试框架运行时,它触发请求构建逻辑,最后交给 assert_eq! 做完整比较,确保默认行为稳定。

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

recent_image_fallback_selects_newest_images_in_chronological_order66–139 ↗
async fn recent_image_fallback_selects_newest_images_in_chronological_order()

作用:这个异步测试检查:当用户说“拿最近几张图来改”时,系统能从对话历史里找出最新的图片,而且返回顺序仍然像时间线一样从早到晚。这样编辑多张图时,图片不会乱序。

数据流:进去的是一段伪造的对话历史,里面混有用户上传图、函数工具输出图、代码模式工具输出图、图片生成结果,以及一个没有对应调用的孤立输出 → 请求构建逻辑从这些历史项里筛出可用图片,取最近 4 张 → 出来的是一个 Edit 请求,里面图片顺序应为 user-2、mcp、code-mode、generated。

调用关系:它在测试中用 vec! 组装一段复杂历史,再通过 assert_eq! 把实际编辑请求和 expected_edit_request 造出的预期请求比较;这个测试覆盖了图片来源混杂时的兜底选图流程。

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

conflicting_image_selectors_return_tool_error142–163 ↗
async fn conflicting_image_selectors_return_tool_error()

作用:这个异步测试确认:用户不能同时说“用这些文件路径里的图”和“用最近几张对话图片”。这两种选图方式互相冲突,系统应该立刻给出清楚错误,而不是猜用户想要哪一种。

数据流:进去的是一个同时包含 referenced_image_paths 和 num_last_images_to_include 的参数 → request_for_call_args 检查到两种选图规则同时出现 → 出来的是错误信息,内容必须是“只能二选一”。

调用关系:它直接调用 request_for_call_args 来走真实的参数校验,然后用 assert_eq! 检查错误文本;vec! 用来构造测试里的路径列表。

调用图:外部调用 3 个(assert_eq!, request_for_call_args, vec!)。

too_many_referenced_image_paths_return_tool_error166–191 ↗
async fn too_many_referenced_image_paths_return_tool_error()

作用:这个异步测试检查:用户一次传入的参考图片路径不能超过 5 个。这样可以避免工具还没调用图片服务,就先读取或上传过多图片,带来性能和接口限制问题。

数据流:进去的是 6 个绝对路径和一个编辑提示词 → request_for_call_args 在读取文件之前先数路径数量 → 出来的是一个错误,明确说明 referenced_image_paths 最多只能有 5 个;文件内容不会被读取。

调用关系:它把大量路径交给 request_for_call_args,专门验证入口处的防线;最后用 assert_eq! 锁定错误文案,防止以后报错变得含糊。

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

recent_image_fallback_requires_requested_count194–217 ↗
async fn recent_image_fallback_requires_requested_count()

作用:这个异步测试确认:如果用户要求使用最近 2 张图,但历史里只有 1 张,系统必须报错,而不是偷偷少用一张。这样用户不会以为两张图都参与了编辑。

数据流:进去的是一个要求最近 2 张图的参数,以及只含 1 张图片的对话历史 → request_for_call_args 尝试从历史里收集图片,发现数量不够 → 出来的是错误信息,说明请求了 2 张但只找到 1 张。

调用关系:它用 vec! 构造最小历史场景,调用 request_for_call_args 触发最近图片兜底逻辑,再用 assert_eq! 确认错误提示准确。

调用图:外部调用 3 个(assert_eq!, request_for_call_args, vec!)。

generated_output_returns_image_input_and_output_hint220–248 ↗
fn generated_output_returns_image_input_and_output_hint()

作用:这个测试检查生成图片的结果怎样返回给对话系统:既要带上图片本身,也可以带上一段保存位置提示。这样后续模型既能“看见”图片,也知道图片可能存到哪里。

数据流:进去的是一段模拟的 base64 图片内容 RESULT,以及 extension_image_generation_output_hint 生成的保存提示 → GeneratedImageOutput 被转成 ResponseInputItem → 出来的是函数调用输出,里面包含一个 InputImage 图片项和一个 InputText 提示文本项。

调用关系:它先调用 extension_image_generation_output_hint 生成短提示,再用 function_payload 提供工具调用载荷,最后用 assert_eq! 核对返回内容;如果返回类型不是函数工具输出或内容格式不对,就用 panic! 让测试失败。

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

generated_output_returns_generated_image_helper_input_in_code_mode251–264 ↗
fn generated_output_returns_generated_image_helper_input_in_code_mode()

作用:这个测试检查代码模式下的图片生成结果格式。代码模式需要的是一小段 JSON 数据,里面有图片 data URL 和保存提示,方便程序继续处理。

数据流:进去的是 GeneratedImageOutput,包含 base64 图片结果和一段 output_hint → code_mode_result 把它整理成 JSON 对象 → 出来的是包含 image_url 和 output_hint 两个字段的 JSON;测试用预期 JSON 做对比。

调用关系:它在测试运行时直接检查 code_mode_result 的输出,并用 assert_eq! 锁定字段名和值,确保代码模式和普通对话模式各自拿到合适格式。

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

generated_output_omits_oversized_output_hint267–291 ↗
fn generated_output_omits_oversized_output_hint()

作用:这个测试确认:如果保存提示太长,返回给模型时会把提示省掉,只保留图片。这样可以避免把过长路径或提示塞进上下文,占用太多空间。

数据流:进去的是一个很长的路径字符串和 base64 图片结果 → extension_image_generation_output_hint 因为提示过大而不给出可用提示,GeneratedImageOutput 再转成响应项 → 出来的是只含 InputImage 的内容列表,没有 InputText 提示。

调用关系:它调用 extension_image_generation_output_hint 制造“提示过长”的场景,用 function_payload 补齐工具载荷,再通过 assert_eq! 检查最终输出;如果响应结构不符合预期,会 panic!。

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

input_image293–298 ↗
fn input_image(image: &str) -> ContentItem

作用:这是测试用的小帮手,用来快速造一条“用户输入图片”的内容项。它让测试不用每次重复写完整的图片结构。

数据流:进去的是一个短字符串,比如 user-1 → 函数用 format! 把它拼进 data:image/png;base64,... 这种图片文本地址里,并把清晰度 detail 留空 → 出来的是 ContentItem::InputImage,可放进伪造的对话历史。

调用关系:它主要服务于最近图片选择相关测试,帮测试数据看起来更短更清楚;它只负责造输入图片项,不参与真正的图片生成。

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

image_output300–305 ↗
fn image_output(image: &str) -> FunctionCallOutputPayload

作用:这是测试用的小帮手,用来快速造一份“工具输出了一张图片”的结果。它模拟外部工具或代码模式把图片交回来的样子。

数据流:进去的是一个短图片标记字符串 → 函数把它拼成 base64 图片 data URL,再放进 FunctionCallOutputContentItem::InputImage → 出来的是 FunctionCallOutputPayload,也就是函数调用输出的包装格式。

调用关系:它通过 from_content_items 把图片内容项包成协议需要的输出载荷,并用 vec! 放进列表;最近图片兜底测试用它来模拟不同工具产生的图片。

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

expected_edit_request307–322 ↗
fn expected_edit_request(prompt: &str, images: &[&str]) -> ImageEditRequest

作用:这是测试用的预期值生成器,用来根据提示词和图片列表造出一份标准的图片编辑请求。它避免每个测试手写一大段重复结构。

数据流:进去的是 prompt 和一组图片短标记 → 函数把每个标记转成 ImageUrl,并填入固定默认值,比如模型 gpt-image-2、自动背景、自动质量、自动尺寸 → 出来的是 ImageEditRequest,供测试拿来和真实结果比较。

调用关系:它服务于编辑请求相关测试,特别是最近图片选择测试;它不调用外部流程,而是把“正确答案”集中写在一个地方,方便 assert_eq! 做对照。

function_payload324–328 ↗
fn function_payload() -> ToolPayload

作用:这是测试用的小帮手,用来造一个最简单的函数工具载荷。图片输出转换时需要这个外壳,但这些测试不关心具体参数,所以只放空 JSON。

数据流:进去不需要额外输入 → 函数创建 ToolPayload::Function,并把 arguments 设成字符串 "{}" → 出来的是可传给输出转换函数的工具载荷。

调用关系:它被 generated_output_returns_image_input_and_output_hint 和 generated_output_omits_oversized_output_hint 调用,用来补齐生成结果转响应项时需要的上下文。

调用图:被 2 处调用(generated_output_omits_oversized_output_hint, generated_output_returns_image_input_and_output_hint)。

ext/memories/src/tests.rs源码 ↗
testtest run

这个文件像一份“验收清单”。记忆扩展会给系统提供一些工具,比如新增临时笔记、列出记忆、读取记忆、搜索记忆;也会把记忆摘要塞进提示词里,让模型能参考以前保存的信息。测试先检查工具名字是否符合接口要求,再检查配置没开、工具没开时不会乱提供工具;配置打开后,应该注册出正确的工具。后半部分用临时目录模拟真实磁盘,验证新增笔记会写成文件、文件名不能带路径以免越界写文件、读取工具能按行读取、搜索工具能处理多个关键词和“几个行内必须全部出现”的模式。最后两个小辅助函数负责在测试里拿到指定记忆工具,并生成带命名空间的工具名。

函数细节16
memory_tool_namespace_matches_responses_api_identifier27–34 ↗
fn memory_tool_namespace_matches_responses_api_identifier()

作用:检查记忆工具使用的命名空间不是空的,而且只包含接口允许的字符。这样工具名拿去给外部响应接口使用时,不会因为格式不合法而出错。

数据流:进去的是代码里定义好的记忆工具命名空间常量 → 测试逐字节检查它是否非空,以及每个字符是不是英文字母、数字、下划线或短横线 → 出来的是测试通过或失败,不改动任何数据。

调用关系:这是最基础的格式防线。它只用断言检查常量本身,不调用其他本文件辅助函数;如果这个测试失败,后面所有依赖命名空间生成工具名的测试都可能没有意义。

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

tools_are_not_contributed_without_thread_config37–48 ↗
fn tools_are_not_contributed_without_thread_config()

作用:确认没有线程级配置时,记忆扩展不会提供任何专用工具。这样可以避免默认情况下突然多出一组模型可调用的工具。

数据流:进去的是一个默认的 MemoriesExtension,以及空的 session 数据和 thread 数据 → 测试调用扩展的 tools 方法,看它会不会给出工具列表 → 出来应当是空列表,测试只做检查,不写文件。

调用关系:它验证扩展在“没有配置”的起点行为。它使用 MemoriesExtension::default 创建扩展,然后直接检查工具贡献结果,是后续“配置打开/关闭”测试的对照组。

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

tools_are_not_contributed_when_disabled51–65 ↗
fn tools_are_not_contributed_when_disabled()

作用:确认即使配置里允许专用工具,只要整个记忆功能被关闭,扩展也不会提供工具。这个测试防止关闭开关失效。

数据流:进去的是默认扩展、一个 thread 数据仓库,以及 enabled 为 false 的配置,配置里还带了测试用 codex_home 路径 → 测试把配置插入 thread 数据,再询问扩展有哪些工具 → 出来应当是空列表,说明关闭总开关生效。

调用关系:它和 tools_are_not_contributed_without_thread_config 一起覆盖“不该出现工具”的情况。它调用测试路径构造函数准备假路径,再交给扩展的 tools 方法判断。

调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, test_path_buf, default)。

tools_are_not_contributed_when_dedicated_tools_disabled68–82 ↗
fn tools_are_not_contributed_when_dedicated_tools_disabled()

作用:确认记忆功能本身打开了,但“专用工具”开关关闭时,也不会贡献那些记忆工具。也就是说,功能开启和工具暴露是两道不同的门。

数据流:进去的是默认扩展、thread 数据仓库,以及 enabled 为 true、dedicated_tools 为 false 的配置 → 测试插入配置后调用 tools → 出来应当是空列表,说明只开记忆不等于开放工具。

调用关系:它补上配置组合里的另一种禁用场景。它不调用本文件辅助函数,只是构造配置后让 MemoriesExtension 自己决定是否贡献工具。

调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, test_path_buf, default)。

tools_are_contributed_when_enabled_with_dedicated_tools85–109 ↗
fn tools_are_contributed_when_enabled_with_dedicated_tools()

作用:确认当记忆功能和专用工具开关都打开时,扩展会提供预期的四个工具。这个测试保证用户真正启用后能用到新增笔记、列出、读取、搜索这些能力。

数据流:进去的是默认扩展、带 enabled 为 true 且 dedicated_tools 为 true 的配置 → 测试调用 tools,取出每个工具的名字 → 出来应当正好是四个带命名空间的工具名,顺序和内容都要匹配。

调用关系:它是前面几个“不提供工具”测试的正向版本。它会用 memory_tool_name 生成期望名字的同类结果,再用断言和扩展实际贡献的工具名比较。

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

install_registers_dedicated_tool_contributor112–139 ↗
fn install_registers_dedicated_tool_contributor()

作用:确认扩展安装到总注册表以后,记忆工具贡献者真的被注册进去了。这样系统启动时通过统一注册机制,也能找到这些工具。

数据流:进去的是一个新的扩展注册表构建器,以及测试线程配置 → 测试调用 crate::install 安装记忆扩展,构建注册表,再从所有工具贡献者那里收集工具名 → 出来应当是同样四个记忆工具名。

调用关系:它测试的不是 MemoriesExtension 单独工作,而是安装流程是否把它接进系统。它调用 crate::install,然后通过 registry.tool_contributors 间接触发工具贡献。

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

ad_hoc_tool_definition_includes_filename_contract142–159 ↗
fn ad_hoc_tool_definition_includes_filename_contract()

作用:检查“新增临时笔记”工具的说明书里,明确写了 filename 参数的格式要求。这样调用工具的一方知道文件名必须长什么样。

数据流:进去的是一个测试用记忆根目录和新增笔记工具名 → 测试用 memory_tool 找到工具,把工具规格序列化成 JSON,再定位到 filename 参数 → 出来要求它是字符串,并且描述里包含类似 YYYY-MM-DDTHH-MM-SS-<slug>.md 的格式说明。

调用关系:它通过 memory_tool 拿到真实工具,而不是手写假的工具。这个测试关注工具对外契约,也就是工具告诉模型或调用者该怎么传参数。

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

prompt_contribution_uses_memory_summary_when_enabled162–194 ↗
async fn prompt_contribution_uses_memory_summary_when_enabled()

作用:确认记忆功能开启时,如果磁盘上有 memory_summary.md,扩展会把里面的内容加入提示词。这样模型在回答时能看到长期记忆摘要。

数据流:进去的是一个临时目录,测试先在其中创建 memories 目录并写入 memory_summary.md → 然后构造 enabled 为 true 的配置,调用扩展的 contribute 方法 → 出来应当得到一个提示词片段,放在 DeveloperPolicy 这个位置,并包含刚写入的摘要文字。

调用关系:它测试的是上下文贡献者能力,而不是工具能力。它使用异步文件操作创建测试现场,然后让 MemoriesExtension 读取摘要并产出提示词片段。

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

add_ad_hoc_note_tool_creates_note_file197–238 ↗
async fn add_ad_hoc_note_tool_creates_note_file()

作用:确认调用“新增临时笔记”工具后,真的会在记忆目录下创建一份笔记文件。这个测试保证工具不是只返回成功,而是确实把内容落盘。

数据流:进去的是临时记忆根目录、工具调用载荷,里面有合法文件名和笔记正文 → 测试通过 memory_tool 找到新增笔记工具,构造 ToolCall 并执行 handle → 出来应当返回空 JSON 响应,同时磁盘上 extensions/ad_hoc/notes 目录下出现对应 md 文件,内容等于传入的 note。

调用关系:它先用 memory_tool 找真实工具,再用 memory_tool_name 填入 ToolCall 里的完整工具名。执行时真正走工具的处理逻辑,最后用读文件检查结果。

调用图:调用 2 个内部函数(memory_tool, memory_tool_name);外部调用 7 个(new, new, assert_eq!, json!, Bytes, tempdir, default)。

add_ad_hoc_note_tool_rejects_paths_as_filenames241–273 ↗
async fn add_ad_hoc_note_tool_rejects_paths_as_filenames()

作用:确认新增笔记工具会拒绝像 ../xxx.md 这种带路径跳转的文件名。这个测试很重要,因为它防止调用者把文件写到记忆目录外面。

数据流:进去的是临时记忆根目录,以及 filename 为 ../... 的工具调用载荷 → 测试执行新增笔记工具 → 出来应当是错误而不是成功,错误信息里要提到 filename 和规定的时间戳文件名格式。

调用关系:它和 add_ad_hoc_note_tool_creates_note_file 是一正一反。两者都通过 memory_tool 找工具、通过 memory_tool_name 生成完整名字,但这个测试期待工具在校验阶段拒绝请求。

调用图:调用 2 个内部函数(memory_tool, memory_tool_name);外部调用 8 个(new, new, assert!, json!, panic!, Bytes, tempdir, default)。

read_tool_reads_memory_file276–322 ↗
async fn read_tool_reads_memory_file()

作用:确认读取工具能从记忆文件中按指定行号和行数读出内容。这样模型不必一次读完整文件,可以只拿需要的一小段。

数据流:进去的是一个临时 MEMORY.md 文件,里面有三行文字;工具调用载荷要求从第 2 行开始最多读 1 行 → 测试执行读取工具 → 出来应当返回路径、读到的第二行、起始行号 2,并标记 truncated 为 true,表示还有内容没读完。

调用关系:它通过 memory_tool 找 READ 工具,用 memory_tool_name 放入 ToolCall。这个测试覆盖的是工具执行后的标准 JSON 响应格式。

调用图:调用 2 个内部函数(memory_tool, memory_tool_name);外部调用 9 个(new, new, assert_eq!, json!, Bytes, tempdir, create_dir_all, write, default)。

search_tool_accepts_multiple_queries325–396 ↗
async fn search_tool_accepts_multiple_queries()

作用:确认搜索工具支持一次传多个关键词,并能告诉调用者每一行匹配了哪些关键词。这样搜索记忆时可以一次找多种线索。

数据流:进去的是包含 alpha、needle 以及两者同时出现的 MEMORY.md,载荷里 queries 是两个关键词,case_sensitive 为 false → 测试执行搜索工具 → 出来应当有三条匹配结果,每条包含文件路径、行号、内容和 matched_queries,且没有下一页游标,也没有截断。

调用关系:它通过 memory_tool 找 SEARCH 工具,再用真实 ToolCall 跑搜索逻辑。它证明新版参数 queries 可以工作,并为后面的旧参数拒绝测试提供对照。

调用图:调用 2 个内部函数(memory_tool, memory_tool_name);外部调用 9 个(new, new, assert_eq!, json!, Bytes, tempdir, create_dir_all, write, default)。

search_tool_accepts_windowed_all_match_mode399–457 ↗
async fn search_tool_accepts_windowed_all_match_mode()

作用:确认搜索工具支持“在几行窗口内所有关键词都出现”的模式。比如一个关键词在第一行、另一个在第三行,只要窗口够大,也算匹配。

数据流:进去的是三行文件:alpha、中间行、needle;载荷要求 match_mode 为 all_within_lines,line_count 为 3 → 测试执行搜索工具 → 出来应当返回一条匹配,内容包含这三行,并标明 alpha 和 needle 都匹配到了。

调用关系:它同样通过 memory_tool 找 SEARCH 工具,但测试的是比普通逐行搜索更复杂的匹配方式。它验证搜索工具能把相邻几行当成一个小窗口来判断。

调用图:调用 2 个内部函数(memory_tool, memory_tool_name);外部调用 9 个(new, new, assert_eq!, json!, Bytes, tempdir, create_dir_all, write, default)。

search_tool_rejects_legacy_single_query460–494 ↗
async fn search_tool_rejects_legacy_single_query()

作用:确认搜索工具拒绝旧版的单个 query 字段,只接受新版 queries 字段。这样接口不会悄悄兼容过时写法,避免调用方误用。

数据流:进去的是一个搜索工具调用载荷,里面只有旧字段 query → 测试执行搜索工具 → 出来应当是错误,错误文字里要包含 unknown field 和 query,说明它明确把这个字段当成非法输入。

调用关系:它通过 memory_tool 和 memory_tool_name 使用真实搜索工具。它和 search_tool_accepts_multiple_queries 配合,说明新接口能用、旧接口会被挡住。

调用图:调用 2 个内部函数(memory_tool, memory_tool_name);外部调用 9 个(new, new, assert!, json!, panic!, Bytes, tempdir, create_dir_all, default)。

memory_tool496–505 ↗
fn memory_tool(memory_root: &Path, tool_name: &str) -> Arc<dyn ToolExecutor<ToolCall>>

作用:这是测试用的小帮手:给它一个记忆根目录和短工具名,它会从真实的记忆工具列表里找出对应工具。这样各个测试不用重复写一大段查找代码。

数据流:进去的是 memory_root 路径和工具短名 → 它先用 memory_tool_name 拼出带命名空间的完整工具名,再用 LocalMemoriesBackend::from_memory_root 做一个本地文件后端,然后调用 crate::tools::memory_tools 创建工具列表并查找目标工具 → 出来是一个可执行工具;如果找不到,就让测试直接失败。

调用关系:它被多个工具测试调用,包括新增笔记、读取、搜索以及工具规格检查。它把测试和真实工具构造流程接起来,内部又把名字生成的工作交给 memory_tool_name。

调用图:调用 3 个内部函数(from_memory_root, memory_tool_name, memory_tools);被 7 处调用(ad_hoc_tool_definition_includes_filename_contract, add_ad_hoc_note_tool_creates_note_file, add_ad_hoc_note_tool_rejects_paths_as_filenames, read_tool_reads_memory_file, search_tool_accepts_multiple_queries, search_tool_accepts_windowed_all_match_mode, search_tool_rejects_legacy_single_query)。

memory_tool_name507–509 ↗
fn memory_tool_name(tool_name: &str) -> ToolName

作用:这是测试用的小帮手:把一个普通工具名变成带记忆命名空间的完整工具名。完整名字能避免不同扩展的工具重名。

数据流:进去的是短工具名字符串 → 它调用 ToolName::namespaced,把 crate::MEMORY_TOOLS_NAMESPACE 和短名组合起来 → 出来是一个 ToolName,供断言或 ToolCall 使用。

调用关系:它被 memory_tool 和多个工具执行测试调用。所有需要比较或填写真实工具名的地方,都靠它保持命名方式一致,避免测试里手动拼错。

调用图:调用 1 个内部函数(namespaced);被 7 处调用(add_ad_hoc_note_tool_creates_note_file, add_ad_hoc_note_tool_rejects_paths_as_filenames, memory_tool, read_tool_reads_memory_file, search_tool_accepts_multiple_queries, search_tool_accepts_windowed_all_match_mode, search_tool_rejects_legacy_single_query)。

memories/write/src/extensions/ad_hoc_tests.rs源码 ↗
testtest run

这个测试文件专门检查“临时/即兴记忆扩展”的说明文件怎么被初始化。它先用临时目录造出一个干净的假环境,就像给程序一间空房子;然后调用种子写入函数,让系统把默认的 instructions.md 放进去。接着它读文件,确认内容确实是内置的默认说明。之后,测试故意把这个文件改成“custom instructions”,模拟用户已经写了自己的内容。最后它再次运行初始化,确认文件内容没有被改回默认值。这个行为很重要,因为初始化代码常常会在启动或配置目录准备时运行;它应该只“补齐缺的东西”,不能像重装系统一样把用户现有文件清空。

函数细节1
seeds_instructions_without_overwriting_existing_file7–36 ↗
async fn seeds_instructions_without_overwriting_existing_file()

作用:这个测试确认默认指令文件只会在不存在时被创建,已经存在时不会被覆盖。它用来防止用户自己写的 ad-hoc instructions 被程序重新初始化时误替换。

数据流:进去的是一个新建的临时目录,它被当成假的程序主目录使用。测试根据这个目录算出 instructions.md 应该出现的位置,先运行一次初始化并读取文件,确认内容等于默认指令;然后把文件改成自定义文字,再运行一次初始化,最后读回文件,确认仍然是自定义文字。出来的结果不是业务数据,而是测试断言:如果内容被覆盖,测试就会失败。

调用关系:它在测试运行时由 Tokio 的异步测试框架自动执行。过程中它会创建临时目录,用 memory_extensions_root 算出记忆扩展的根路径,用文件写入函数模拟用户修改,再用断言检查两次读取结果是否符合预期;这些步骤一起证明初始化函数既能补文件,又尊重已有文件。

调用图:外部调用 4 个(new, assert_eq!, memory_extensions_root, write)。

MCP 配置与传输集成

这些文件测试 MCP 配置解析、目录解析、连接管理、扩展覆盖层,以及本地和远程场景下的完整客户端传输行为。

codex-mcp/src/plugin_config_tests.rs源码 ↗
testtest

这个文件专门检查 parse_plugin_mcp_config 这个配置解析入口。MCP 可以理解成一种让插件提供工具服务的协议;这里测试的是插件声明的服务器该怎么变成系统内部的 McpServerConfig。测试重点有几类:本地插件声明时,路径要按插件根目录整理;执行器环境接管时,environment_id 必须被强制改成执行器自己的;cwd,也就是运行命令时所在目录,不能用 ../ 跑出插件包;环境变量也要分清 local 和 remote,避免把只该在本机用的变量交给远程,或反过来。文件里还有两个小帮手:plugin_root 造一个假的插件目录,stdio_server 造一份期望中的标准输入输出服务器配置。整体就像验收一张“施工图”:既检查合法图纸能照章落地,也检查危险写法会被明确拦下。

函数细节9
plugin_root16–20 ↗
fn plugin_root() -> PathBuf

作用:这个小函数给测试造出一个固定的“插件根目录”。这样每个测试都用同一个假位置,不用到处手写路径。

数据流:进去不需要参数;它读取当前程序运行目录,也就是 current_dir,然后在后面接上 plugin-root;出来是一个 PathBuf,可以理解成一条可拼接、可保存的文件夹路径。它不改动磁盘,只是拼出路径字符串。

调用关系:多个测试一开始都会先叫它来拿插件根目录,然后再把这个目录交给 parse_plugin_mcp_config。它自己只把取当前目录这件事交给系统库 current_dir,属于测试里的基础道具。

调用图:被 7 处调用(declared_placement_preserves_local_plugin_normalization, environment_placement_forces_authority_and_defaults_null_cwd, environment_placement_rejects_orchestrator_env_vars, environment_placement_rejects_relative_cwd_that_escapes_package, environment_placement_resolves_relative_cwd_beneath_plugin_root, local_environment_placement_preserves_local_env_vars, local_environment_placement_rejects_remote_env_vars);外部调用 1 个(current_dir)。

stdio_server22–51 ↗
fn stdio_server(
    command: &str,
    environment_id: &str,
    cwd: &Path,
    env_vars: Vec<McpServerEnvVar>,
) -> McpServerConfig

作用:这个小函数帮测试快速造一份“通过标准输入输出启动的 MCP 服务器配置”。标准输入输出可以理解成程序之间用命令行管道说话,而不是走网页接口。

数据流:进去的是命令名、环境编号、运行目录和环境变量列表;它把这些塞进 McpServerConfig 里,并把其他开关设成测试需要的默认值,比如启用、非必需、没有 OAuth;出来是一份完整的服务器配置,用来和真实解析结果比较。

调用关系:它是测试里减少重复代码的模板工具。根据调用图,它由 declared_placement_preserves_local_plugin_normalization 使用,帮这个测试先搭好“应该得到什么”的答案。它内部只调用一些标准库方法来新建集合、复制路径。

调用图:被 1 处调用(declared_placement_preserves_local_plugin_normalization);外部调用 3 个(new, to_path_buf, new)。

declared_placement_preserves_local_plugin_normalization54–116 ↗
fn declared_placement_preserves_local_plugin_normalization()

作用:这个测试确认:插件自己声明 MCP 服务器时,解析器会保留本地插件的语义,同时把相对路径整理到插件根目录下面。

数据流:进去的是一段写在测试里的 JSON 配置,里面有一个 stdio 服务器和一个 http 服务器;测试先准备插件根目录和期望结果,再调用 parse_plugin_mcp_config;出来的解析结果必须等于期望:demo 的 cwd 被拼到 plugin-root/scripts,hosted 的 HTTP 地址和 OAuth 客户端编号被保留下来,并且没有错误。

调用关系:它是针对“Declared,也就是插件主动声明”这种放置方式的验收测试。流程上先用 plugin_root 和 stdio_server 造期望值,再把 JSON 交给 parse_plugin_mcp_config,最后用 assert_eq! 把实际结果和期望结果逐项对比。

调用图:调用 2 个内部函数(plugin_root, stdio_server);外部调用 4 个(new, new, assert_eq!, parse_plugin_mcp_config)。

environment_placement_forces_authority_and_defaults_null_cwd119–162 ↗
fn environment_placement_forces_authority_and_defaults_null_cwd()

作用:这个测试确认:当 MCP 服务器属于某个执行器环境时,配置里自己写的 environment_id 不能算数,必须被执行器指定的编号覆盖。它还检查 cwd 写成 null 时会默认回插件根目录。

数据流:进去的是一段 JSON:服务器自称 environment_id 是 local,cwd 是 null,还声明了两个环境变量;测试把它按 Environment placement 解析,并指定真正的环境编号 executor-1;出来的结果必须显示环境编号变成 executor-1,运行目录变成 plugin_root,环境变量来源被整理成 remote,并且没有错误。

调用关系:这个测试站在“执行器接管插件”的场景里,先拿 plugin_root,再把配置交给 parse_plugin_mcp_config。它用 assert_eq! 验证解析器有没有强制执行环境归属规则,防止插件自己冒充别的运行环境。

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

environment_placement_resolves_relative_cwd_beneath_plugin_root165–191 ↗
fn environment_placement_resolves_relative_cwd_beneath_plugin_root()

作用:这个测试确认:在执行器环境里,如果 cwd 写的是普通相对目录,比如 scripts,解析器会把它安全地放到插件根目录下面。

数据流:进去的是 JSON 配置,其中 cwd 是 scripts;测试给出插件根目录和执行器环境编号 executor-1;parse_plugin_mcp_config 处理后,出来的服务器配置应该把运行目录变成 plugin_root/scripts,环境编号也用 executor-1,并且错误列表为空。

调用关系:它验证的是路径整理的正常情况。测试先用 plugin_root 得到基准目录,再调用 parse_plugin_mcp_config,最后用 assert_eq! 确认相对目录没有被原样乱用,而是被压到插件包范围内。

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

environment_placement_rejects_relative_cwd_that_escapes_package194–218 ↗
fn environment_placement_rejects_relative_cwd_that_escapes_package()

作用:这个测试确认:插件不能用 ../outside 这种路径把运行目录跳出自己的插件包。这样做很危险,可能让插件去碰不该碰的文件。

数据流:进去的是一段 cwd 为 ../outside 的 JSON;解析器拿到插件根目录和执行器环境编号后,会检查这个相对路径是否还在插件根目录内;出来的结果应该没有任何服务器配置,只有一条错误,明确说明这个 cwd 必须留在插件根目录里。

调用关系:它是路径安全规则的反面测试。测试用 plugin_root 得到错误消息里要显示的根目录,再调用 parse_plugin_mcp_config,最后用 assert_eq! 确认危险配置被拒绝,而不是被悄悄接受。

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

environment_placement_rejects_orchestrator_env_vars221–244 ↗
fn environment_placement_rejects_orchestrator_env_vars()

作用:这个测试确认:执行器拥有的插件不能要求使用 local 来源的环境变量。local 可以理解成本机协调者那边的变量,远程执行器不该随便拿。

数据流:进去的是 JSON,其中 env_vars 写了 TOKEN,并把 source 标成 local;解析器按 executor-1 这个执行器环境来处理;出来应该是空的服务器列表和一条错误,告诉用户 executor-owned plugin 不能用 local 来源的 TOKEN。

调用关系:它检查环境变量来源的安全边界。测试先取 plugin_root,再把配置交给 parse_plugin_mcp_config,最后通过 assert_eq! 确认解析器没有把本地敏感变量放进执行器环境。

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

local_environment_placement_preserves_local_env_vars247–279 ↗
fn local_environment_placement_preserves_local_env_vars()

作用:这个测试确认:如果运行环境就是本地默认环境,那么本地环境变量应该被保留下来,不该被误判成非法。

数据流:进去的是 JSON,env_vars 里一个只写名字 TOKEN,另一个写明 OTHER 来自 local;解析器按默认本地环境编号处理;出来的配置应该保留 TOKEN 这种简单写法,也保留 OTHER 的 local 来源,服务器正常生成,错误列表为空。

调用关系:它验证本地环境的正常路径。测试用 plugin_root 设置运行基准,把 JSON 交给 parse_plugin_mcp_config,再用 assert_eq! 确认解析器不会把合法的本地变量当成远程风险拦掉。

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

local_environment_placement_rejects_remote_env_vars282–304 ↗
fn local_environment_placement_rejects_remote_env_vars()

作用:这个测试确认:本地环境里的插件不能声明 remote 来源的环境变量。因为本地运行时不应该假装自己能拿到远程执行器的变量。

数据流:进去的是 JSON,其中 TOKEN 的 source 是 remote;解析器按默认本地环境处理;出来应该没有服务器配置,只有一条错误,说明 local environment 里不能使用 remote 来源的 TOKEN。

调用关系:它是本地环境变量规则的反面测试。流程是先拿 plugin_root,再调用 parse_plugin_mcp_config,最后用 assert_eq! 检查远程来源变量被明确拒绝,和前一个保留本地变量的测试形成一正一反的保护网。

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

ext/mcp/src/executor_plugin/provider_tests.rs源码 ↗
testtest

这个测试文件像一个“假文件柜”:它做了一个 SyntheticExecutorFileSystem,只允许读一个指定的 MCP 配置文件,并把每次读了什么路径都记下来。MCP 可以理解为模型和外部工具服务之间的连接配置;这里要保证插件的 MCP 配置是在执行器的文件系统里读取的,因为执行器可能有沙箱、远程环境或权限限制,不能随便从本机硬盘拿文件。测试分三种情况:声明了配置时,应该读到配置并生成可用的本地命令型服务器;默认配置文件不存在时,应该当作没有服务器,不报错;声明的配置内容不是 JSON 时,应该明确报解析错误。很多文件系统操作在这个假文件系统里都故意报“不支持”,这样一旦被测代码走错路,测试就会立刻失败。

函数细节15
SyntheticExecutorFileSystem::unsupported40–45 ↗
fn unsupported() -> FileSystemResult<T>

作用:这是测试用的统一“此路不通”函数。凡是这个假文件系统不希望被调用的操作,都会用它返回一个“不支持”的错误。

数据流:进去的是想要的返回类型标记,但没有真正数据;它创建一个输入输出错误,说明这个操作不该在这些测试里出现;出来的是一个失败结果,不改任何状态。

调用关系:它被 canonicalize、read_file_stream、write_file、create_directory、get_metadata、read_directory、remove、copy 这些假文件系统方法使用。这样测试就能证明被测代码只需要 read_file,不应该碰别的文件系统能力。

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

SyntheticExecutorFileSystem::canonicalize49–55 ↗
fn canonicalize(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, PathUri>

作用:这是“把路径整理成标准路径”的接口实现,但在这个测试里故意不支持。它的存在只是为了满足执行器文件系统这个接口。

数据流:进去的是一个路径和可选的沙箱信息;它没有真正整理路径,而是立刻包装一个异步失败结果;出来的是“不支持”的错误。

调用关系:如果 load_from_file_system 试图先规范化路径,这个方法会被触发并让测试失败。现在它通过调用 unsupported 来充当警报器。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::read_file57–75 ↗
fn read_file(
        &'a self,
        path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, Vec<u8>>

作用:这是这个假文件系统唯一真正干活的方法:模拟读取 MCP 配置文件。它还会记录被读过的路径,方便测试确认读取行为没有越界。

数据流:进去的是一个路径和可选沙箱信息;它先把路径转成绝对路径,再把这个路径记进 reads 列表;如果路径不是预设的配置文件,就返回“找不到”;如果路径正确且有内容,就把字符串内容变成字节返回;如果内容被设成空,也返回“找不到”。

调用关系:load_from_file_system 在读取插件 MCP 配置时会调用它。三个测试都依靠它来确认实际读了哪个配置路径,以及配置存在、缺失、损坏时被测代码的反应。

调用图:调用 1 个内部函数(to_abs_path);外部调用 3 个(pin, new, clone)。

SyntheticExecutorFileSystem::read_file_stream77–83 ↗
fn read_file_stream(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream>

作用:这是“流式读取文件”的接口实现,但这些测试不需要它。它故意返回不支持,用来防止被测代码换了不该用的读取方式。

数据流:进去的是路径和可选沙箱信息;它不打开文件流,而是返回一个异步的“不支持”错误;没有状态变化。

调用关系:它通过 unsupported 报错。若 load_from_file_system 没有用普通 read_file,而是改用流式读取,测试会暴露出来。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::write_file85–92 ↗
fn write_file(
        &'a self,
        _path: &'a PathUri,
        _contents: Vec<u8>,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, ()>

作用:这是“写文件”的接口实现,但 MCP 配置加载测试只应该读文件,不应该写文件。所以这里故意不支持。

数据流:进去的是路径、要写入的字节内容和可选沙箱信息;它完全不写入,直接返回“不支持”的错误;文件系统状态不会改变。

调用关系:它通过 unsupported 充当护栏。如果被测的配置读取流程意外尝试写文件,这个测试替身会马上报错。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::create_directory94–101 ↗
fn create_directory(
        &'a self,
        _path: &'a PathUri,
        _options: CreateDirectoryOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'

作用:这是“创建目录”的接口实现,但配置读取不应该创建目录。这里用不支持错误来确保流程没有副作用。

数据流:进去的是目录路径、创建选项和可选沙箱信息;它不创建任何东西,只返回“不支持”;没有状态变化。

调用关系:它调用 unsupported。它的作用是保证 load_from_file_system 在这些场景里只读配置,不做准备目录之类的额外动作。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::get_metadata103–109 ↗
fn get_metadata(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, FileMetadata>

作用:这是“查看文件信息”的接口实现,比如大小、类型、时间等。但这些测试不希望加载器依赖这个操作。

数据流:进去的是路径和可选沙箱信息;它不查询任何元数据,直接返回“不支持”的错误;没有输出有效文件信息。

调用关系:它调用 unsupported。如果被测代码为了判断配置是否存在而改查元数据,测试会失败,从而提醒这里的预期读取路径变了。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::read_directory111–117 ↗
fn read_directory(
        &'a self,
        _path: &'a PathUri,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>>

作用:这是“列目录”的接口实现,但测试中的 MCP 配置路径是明确的,不应该扫描目录。这里故意不支持。

数据流:进去的是目录路径和可选沙箱信息;它不列出目录内容,返回“不支持”;不产生目录条目。

调用关系:它调用 unsupported。它防止 load_from_file_system 通过扫目录来找配置,而不是按约定路径或声明路径读取。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::remove119–126 ↗
fn remove(
        &'a self,
        _path: &'a PathUri,
        _options: RemoveOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> ExecutorFileSystemFuture<'a, ()>

作用:这是“删除文件或目录”的接口实现,但配置加载绝不应该删除东西。这里用错误保护测试环境。

数据流:进去的是要删除的路径、删除选项和可选沙箱信息;它不删除任何内容,直接返回“不支持”;状态不变。

调用关系:它调用 unsupported。它说明这个假文件系统只为读取测试服务,任何删除行为都会被当成异常。

调用图:外部调用 2 个(pin, unsupported)。

SyntheticExecutorFileSystem::copy128–136 ↗
fn copy(
        &'a self,
        _source_path: &'a PathUri,
        _destination_path: &'a PathUri,
        _options: CopyOptions,
        _sandbox: Option<&'a FileSystemSandboxContext>,
    ) -> Ex

作用:这是“复制文件”的接口实现,但 MCP 配置加载不应该复制文件。这里故意不支持,避免隐藏的副作用。

数据流:进去的是源路径、目标路径、复制选项和可选沙箱信息;它不复制,直接返回“不支持”;没有文件被创建或改动。

调用关系:它调用 unsupported。如果被测流程意外把配置复制到别处,这个方法会让测试立刻失败。

调用图:外部调用 2 个(pin, unsupported)。

reads_declared_config_only_through_executor_file_system140–188 ↗
async fn reads_declared_config_only_through_executor_file_system()

作用:这个测试确认:当插件清楚声明了 MCP 配置文件时,加载器只通过执行器文件系统读取这个文件。它还检查读出来的本地命令型 MCP 服务器配置是否被正确补全。

数据流:开始时它创建一个临时插件根目录,但这个目录在真实磁盘上并不存在;然后构造插件描述、假文件系统和一份 JSON 配置;调用 load_from_file_system 后,得到服务器列表;最后它检查只生成了 demo 这个本地命令服务器,并且读取记录里只有声明的 config/mcp.json。

调用关系:它调用 resolved_plugin 生成测试插件,调用 load_from_file_system 执行真正被测逻辑,再用 reads 查看假文件系统记录。它是证明“不能绕过执行器文件系统读真实磁盘”的核心测试。

调用图:调用 2 个内部函数(resolved_plugin, from_absolute_path_checked);外部调用 6 个(new, new, assert!, assert_eq!, load_from_file_system, tempdir)。

missing_default_config_is_empty191–209 ↗
async fn missing_default_config_is_empty()

作用:这个测试确认:如果插件没有声明 MCP 配置文件,就去默认位置找;默认文件不存在时,不应该报错,只表示没有 MCP 服务器。

数据流:开始时它创建临时插件根路径,并把默认配置路径放进假文件系统;假文件系统被设置成这个路径也没有内容;调用 load_from_file_system 后,结果应是空列表;读取记录应显示只尝试读过默认配置文件。

调用关系:它用 resolved_plugin 创建一个没有 mcp_servers 路径的插件,再交给 load_from_file_system。它覆盖的是“可选默认配置缺失”这条宽容路径。

调用图:调用 2 个内部函数(resolved_plugin, from_absolute_path_checked);外部调用 5 个(new, new, assert_eq!, load_from_file_system, tempdir)。

malformed_declared_config_is_an_error212–241 ↗
async fn malformed_declared_config_is_an_error()

作用:这个测试确认:如果插件明确声明了配置文件,但文件内容不是合法 JSON,就必须报错,不能悄悄忽略。这样插件作者才能知道配置写坏了。

数据流:进去的是一个声明了 mcp.json 的测试插件和一个会返回“{not-json”的假文件系统;调用 load_from_file_system 后,它期待得到错误;然后拆开错误,确认错误里带着插件 ID 和出错路径;最后确认确实只读了这个配置文件。

调用关系:它调用 resolved_plugin 准备插件,再调用 load_from_file_system 触发解析。它和 missing_default_config_is_empty 形成对比:默认文件缺失可以忽略,但声明文件写坏必须失败。

调用图:调用 2 个内部函数(resolved_plugin, from_absolute_path_checked);外部调用 6 个(new, new, assert_eq!, panic!, load_from_file_system, tempdir)。

resolved_plugin243–267 ↗
fn resolved_plugin(
    plugin_root: &AbsolutePathBuf,
    mcp_servers: Option<AbsolutePathBuf>,
) -> ResolvedPlugin

作用:这是测试里的小工厂函数,用来快速造一个“已经解析好的插件”对象。这样每个测试不用重复写一大段插件清单。

数据流:进去的是插件根目录和可选的 MCP 配置路径;它填好插件 ID、环境 ID、插件根目录、manifest 路径和插件清单;出来的是一个 ResolvedPlugin,表示测试里可直接使用的插件描述。

调用关系:它被三个测试函数调用,为 load_from_file_system 提供统一的插件输入。它内部调用 from_environment 来生成正式的插件对象,并把 mcp_servers 路径放进插件清单。

调用图:调用 2 个内部函数(from_environment, join);被 3 处调用(malformed_declared_config_is_an_error, missing_default_config_is_empty, reads_declared_config_only_through_executor_file_system);外部调用 2 个(new, clone)。

reads269–275 ↗
fn reads(file_system: &SyntheticExecutorFileSystem) -> Vec<AbsolutePathBuf>

作用:这个辅助函数把假文件系统记录下来的“读过哪些路径”取出来。测试用它来核对加载器到底碰了哪些文件。

数据流:进去的是 SyntheticExecutorFileSystem;它拿到 reads 里的互斥锁,互斥锁就是防止多个任务同时改同一份列表的一把锁;然后复制路径列表并返回;原始记录不被清空。

调用关系:三个测试在调用 load_from_file_system 之后使用它。它把假文件系统内部的记录变成可比较的普通列表,方便 assert_eq! 做断言。

codex-mcp/src/mcp/mod_tests.rs源码 ↗
testtest

MCP 可以理解成一套“让 Codex 接外部工具和应用”的接口。这个测试文件像一张安全检查表:它先造出一个假的 MCP 配置,再分别验证几个容易出错的地方。比如,服务器名变成工具名前缀时,只能替换不合适的字符,不能偷偷改大小写;权限提示能不能自动通过,要同时看用户的批准策略、文件/网络权限,以及工具自己的批准模式;插件提供了哪些连接器、哪些 MCP 服务器,也要被正确记账,避免把本地插件和远端选中的插件混在一起。它还检查 Codex Apps 的 MCP 地址拼接规则,尤其是旧路径仍然要保留。最后,它确认把配置合并成“实际可用服务器列表”时,不会把用户运行时配置的服务器覆盖掉。这些测试本身不服务线上用户,但能防止后续改动破坏关键行为。

函数细节11
test_mcp_config20–39 ↗
fn test_mcp_config(codex_home: PathBuf) -> McpConfig

作用:造一个测试用的 MCP 配置对象,省得每个测试都从零写一大堆默认值。它就像测试里的“标准空白表格”,需要时再往里面填特殊内容。

数据流:进去的是一个 codex_home 路径,也就是测试假装的 Codex 主目录。函数把这个路径和一批默认设置装进 McpConfig:默认 ChatGPT 地址、默认认证存储方式、默认批准策略、空的服务器目录、空的插件能力列表等。出来的是一个可以继续修改的 McpConfig;它不读真实配置文件,也不启动服务器。

调用关系:它是多个测试的准备步骤。tool_plugin_provenance_collects_app_and_mcp_sources、selected_mcp_attribution_does_not_join_an_unrelated_local_summary 和 effective_mcp_servers_preserve_runtime_servers 会先调用它拿到基础配置,再改其中几项来测试特定场景。它内部依赖一些 default 和 allow_any 之类的默认值构造函数,让测试只关心自己要验证的点。

调用图:调用 2 个内部函数(allow_any, default);被 3 处调用(effective_mcp_servers_preserve_runtime_servers, selected_mcp_attribution_does_not_join_an_unrelated_local_summary, tool_plugin_provenance_collects_app_and_mcp_sources);外部调用 4 个(default, new, default, default)。

qualified_mcp_tool_name_prefix_sanitizes_server_names_without_lowercasing42–47 ↗
fn qualified_mcp_tool_name_prefix_sanitizes_server_names_without_lowercasing()

作用:检查 MCP 工具名前缀的生成规则:服务器名里的不合适字符要被替换,但原来的大小写要保留。这样能避免工具名冲突,也避免把用户配置的名字改得认不出来。

数据流:进去的是测试写死的服务器名 Some-Server。被测逻辑会把它变成工具名前缀,把连字符替换成下划线,并加上 mcp__ 和末尾的双下划线。测试用 assert_eq! 比较结果,确认出来的是 mcp__Some_Server__。

调用关系:这是一个独立的小规则测试。它直接验证工具命名前缀函数的输出,不需要搭建完整配置,也不把工作交给其他测试辅助函数。

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

mcp_prompt_auto_approval_honors_unrestricted_managed_profiles50–80 ↗
fn mcp_prompt_auto_approval_honors_unrestricted_managed_profiles()

作用:检查“权限提示是否可以自动批准”的规则,在完全放开的托管权限下是否按预期工作。它确保只有真正安全策略允许时,系统才会跳过人工确认。

数据流:进去的是几组批准策略和权限档案:一种是 AskForApproval::Never,意思是不要求用户批准;权限档案里文件系统是 Unrestricted,意思是文件访问不受限,网络分别测试开启和受限。函数把这些输入交给自动批准判断逻辑,然后用 assert! 确认结果:不要求批准且文件权限完全放开时可以自动批准;只读权限不行;如果策略是 OnRequest,也就是请求时要问用户,即使权限很宽也不能自动批准。

调用关系:它专门覆盖权限档案这一块的判断。测试本身只负责摆出场景和检查真假,核心判断交给 mcp_permission_prompt_is_auto_approved。

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

mcp_prompt_auto_approval_honors_approved_tools_in_all_permission_modes83–113 ↗
fn mcp_prompt_auto_approval_honors_approved_tools_in_all_permission_modes()

作用:检查如果某个工具已经被明确批准,那么不管当前整体批准策略是哪种,都应该自动通过。这样用户或系统已经认可的工具不会被重复打断。

数据流:进去的是一组不同的 AskForApproval 策略,包括信任外才问、失败时问、每次请求问、细粒度批准和永不询问。每一轮都配一个只读权限档案,并在上下文里标明工具批准模式是 Approve。测试确认这些情况都会返回 true。最后它又测试 Auto 模式在 OnRequest 策略下不会被当成明确批准,结果应为 false。

调用关系:它围绕 mcp_permission_prompt_is_auto_approved 做批量场景验证。GranularApprovalConfig 用来构造“细粒度批准”场景,assert! 用来确认每个场景的布尔结果符合预期。

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

mcp_prompt_auto_approval_rejects_auto_mode_in_default_permission_mode116–124 ↗
fn mcp_prompt_auto_approval_rejects_auto_mode_in_default_permission_mode()

作用:检查工具的 Auto 模式不会在默认需要询问的权限模式下被误认为“已经批准”。这是为了避免系统太激进,绕过用户确认。

数据流:进去的是 OnRequest 批准策略、只读权限档案,以及工具批准模式 Auto。自动批准判断逻辑处理后,测试要求结果必须是 false。也就是说,Auto 只是自动模式,不等于用户明确点了批准。

调用关系:这是上一类权限测试的一个窄场景补充。它直接调用自动批准判断逻辑,用 assert! 确认这个边界条件没有被放宽。

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

tool_plugin_provenance_collects_app_and_mcp_sources127–187 ↗
fn tool_plugin_provenance_collects_app_and_mcp_sources()

作用:检查系统能不能正确记录“某个工具或服务器来自哪个插件”。这很重要,因为用户看到工具时,需要知道它背后的来源,系统也要能按插件做权限和展示。

数据流:开始时它用 test_mcp_config 造一个空配置。然后用 builder 建一个 MCP 服务器目录,注册一个来自插件 alpha 的服务器。接着往配置里放两个插件能力摘要:alpha 和 beta,它们声明了各自提供的连接器和 MCP 服务器名。被测的来源汇总逻辑会读这些信息,生成 ToolPluginProvenance:按连接器 ID 查到插件显示名,按 MCP 服务器名查到插件显示名和插件 ID。测试最后确认 alpha 被登记为 MCP 服务器来源,beta 因为没有实际注册到目录里,所以不能通过服务器名查到插件 ID。

调用关系:它先调用 test_mcp_config 准备基础配置,再通过 ResolvedMcpCatalog::builder 和 McpServerRegistration::from_plugin 拼出测试目录。真正的汇总工作交给 tool_plugin_provenance,测试用 assert_eq! 和 plugin_id_for_mcp_server_name 来核对结果。

调用图:调用 4 个内部函数(new, from_plugin, builder, test_mcp_config);外部调用 3 个(new, assert_eq!, vec!)。

selected_mcp_attribution_does_not_join_an_unrelated_local_summary190–228 ↗
fn selected_mcp_attribution_does_not_join_an_unrelated_local_summary()

作用:检查“用户选中的插件服务器”和“本地插件能力摘要”不会因为 ID 或名字相似就被错误合并。这样可以防止界面或权限里显示错插件来源。

数据流:它先用 test_mcp_config 建基础配置,再注册一个名叫 github 的 MCP 服务器,来源是选中的插件,显示名是 Executor GitHub。随后它又放入一个本地插件摘要,使用同一个 config_name,但显示名是 Local GitHub。被测逻辑读取后,结果必须只把 github 归到选中的 Executor GitHub,并标记 github 是 selected plugin MCP server。它不能把 Local GitHub 混进去。

调用关系:这个测试用 from_selected_plugin 构造“被选择的插件服务器”场景,用 builder 放进目录。之后调用 tool_plugin_provenance 汇总来源,再通过 assert_eq! 和 is_selected_plugin_mcp_server 验证没有发生错误合并。

调用图:调用 4 个内部函数(new, from_selected_plugin, builder, test_mcp_config);外部调用 4 个(new, assert!, assert_eq!, vec!)。

codex_apps_mcp_url_for_base_url_keeps_existing_paths231–248 ↗
fn codex_apps_mcp_url_for_base_url_keeps_existing_paths()

作用:检查根据基础网址拼出 Codex Apps MCP 地址时,会尊重已有路径。没有这条规则,配置了自定义后端路径的环境可能会被拼成错误地址。

数据流:进去的是几种基础 URL:有的已经带 backend-api,有的是 chat.openai.com,有的是本地 localhost 并带 api/codex 路径,有的 localhost 没带路径。被测函数会按规则补上 wham/apps 或 apps 等后缀。测试把每个输出和期望字符串比较,确认路径没有被随便丢掉或重复拼错。

调用关系:这是 URL 拼接规则的单元测试。它不需要完整 MCP 配置,只直接检查 codex_apps_mcp_url_for_base_url 的输入输出。

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

codex_apps_server_config_uses_legacy_codex_apps_path251–260 ↗
fn codex_apps_server_config_uses_legacy_codex_apps_path()

作用:检查 Codex Apps MCP 服务器配置仍然使用旧的、兼容的访问路径。这样旧后端或旧部署不会因为新代码改路径而失效。

数据流:进去的是基础地址 https://chatgpt.com 和没有产品 SKU 的设置。被测配置生成函数会返回一个 MCP 服务器配置。测试从里面取出传输方式,要求它必须是 StreamableHttp,也就是通过 HTTP 流式通信;然后检查 URL 必须是 https://chatgpt.com/backend-api/wham/apps。如果传输方式不是预期类型,测试会直接 panic。

调用关系:它直接验证 codex_apps_mcp_server_config 生成的配置内容。URL 规则间接依赖 Codex Apps 地址拼接逻辑,测试用 assert_eq! 保证旧路径没有被改掉。

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

codex_apps_server_config_forwards_configured_product_sku_header263–283 ↗
fn codex_apps_server_config_forwards_configured_product_sku_header()

作用:检查如果配置了产品 SKU,Codex Apps MCP 请求会带上对应的 HTTP 头。HTTP 头可以理解成请求附带的小纸条,后端用它识别产品或套餐。

数据流:进去的是基础地址 https://chatgpt.com 和产品 SKU tpp。配置生成函数返回服务器配置后,测试取出 HTTP 传输配置,查看 http_headers 里是否有 X-OpenAI-Product-Sku: tpp,同时确认没有使用环境变量形式的 headers。若传输类型不对,就 panic。

调用关系:它聚焦 codex_apps_mcp_server_config 里的请求头设置。这个测试补足了上一条 URL 测试,确认生成出来的不只是地址对,请求附带信息也对。

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

effective_mcp_servers_preserve_runtime_servers286–390 ↗
async fn effective_mcp_servers_preserve_runtime_servers()

作用:检查计算“最终生效的 MCP 服务器列表”时,不会丢掉用户运行时配置的服务器。否则用户明明配置了 sample 或 docs,最后可能只剩系统内置的 Codex Apps。

数据流:它先创建临时目录当作 Codex 主目录,用 test_mcp_config 做基础配置,并打开 apps_enabled。然后创建一个假的 ChatGPT 登录身份。接着用目录 builder 注册三个服务器:用户的 sample、配置里的 docs,以及内置的 Codex Apps。被测的 effective_mcp_servers 会读取配置和认证信息,生成最终服务器表。测试再分别取出 sample、docs、codex apps,确认它们都还存在,并且各自 HTTP URL 分别保持为用户给的地址、docs 地址和 Codex Apps 旧路径。

调用关系:这是本文件里最接近真实流程的集成式测试。它调用 test_mcp_config 准备配置,用 tempdir 隔离测试文件位置,用 create_dummy_chatgpt_auth_for_testing 提供假认证,用 from_config 和 builder 构造服务器目录,最后把核心合并工作交给 effective_mcp_servers,并用断言确认合并没有覆盖运行时服务器。

调用图:调用 4 个内部函数(from_config, builder, test_mcp_config, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(new, assert_eq!, panic!, tempdir)。

codex-mcp/src/catalog_tests.rs源码 ↗
testtest run

MCP 服务器可以来自配置文件、插件、用户选中的插件、扩展、兼容旧格式的入口等。现实里它们可能都叫同一个名字,比如都叫 docs,这时系统必须知道到底用哪一个;否则用户可能连到错误的服务,或者明明禁用了却又被重新打开。这个测试文件先造出一些假的服务器配置和假的来源,再把它们按不同顺序注册进 ResolvedMcpCatalog 的构建器里,最后检查结果是不是符合规则。它重点验证几件事:优先级高的来源会赢;同类来源冲突时按插入顺序决定;禁用有时会像“否决票”一样继续影响后来的覆盖;但被禁用的“用户选中插件”不会阻止运行时扩展接管。文件里的小 helper 函数只是为了让测试读起来像故事,不用每次重复写一大坨配置。

函数细节16
server18–46 ↗
fn server(url: &str) -> McpServerConfig

作用:造一个固定模板的假 MCP 服务器配置,测试里只需要传入网址就能得到完整配置。这样每个测试能专注看“目录怎么选择服务器”,不用反复写超长的配置字段。

数据流:输入一个网址字符串 → 函数把它放进 HTTP 传输配置里,并填好默认环境、启用状态、超时时间、工具权限、启用/禁用工具列表等测试用字段 → 输出一个 McpServerConfig;它不改外部状态,只返回一份新配置。

调用关系:几乎所有测试都会先调用它来准备候选服务器。它内部用 Duration::from_secs 设置超时时间,用集合和列表宏填工具配置,然后这些配置会被 from_extension、from_plugin、from_config 等注册函数包成不同来源的服务器。

调用图:被 8 处调用(disabled_discovered_plugin_remains_a_veto_for_runtime_overlays, disabled_selected_plugin_does_not_veto_runtime_overlays, disabled_veto_only_disables_the_winning_registration, disabled_winner_remains_a_veto_when_the_catalog_is_extended, earlier_plugin_wins_with_an_explicit_conflict, equal_precedence_uses_insertion_order_not_source_identity, selected_plugins_override_discovered_plugins_but_not_config, source_precedence_preserves_the_winning_registration);外部调用 3 个(from_secs, from, vec!)。

plugin48–50 ↗
fn plugin(plugin_id: &str) -> McpPluginAttribution

作用:造一个假的插件身份,用来表示“这个服务器是某个插件贡献的”。测试里用插件 ID 同时当作显示名和标识,简单直接。

数据流:输入一个插件 ID 字符串 → 把这个 ID 转成字符串,并交给 McpPluginAttribution::new 创建插件归属信息 → 输出一个 McpPluginAttribution。

调用关系:测试注册插件服务器时会直接用它;plugin_source 和 selected_plugin_source 也会借它先造插件身份,再包装成普通插件来源或用户选中插件来源。

调用图:调用 1 个内部函数(new);被 7 处调用(disabled_discovered_plugin_remains_a_veto_for_runtime_overlays, disabled_selected_plugin_does_not_veto_runtime_overlays, earlier_plugin_wins_with_an_explicit_conflict, plugin_source, selected_plugin_source, selected_plugins_override_discovered_plugins_but_not_config, source_precedence_preserves_the_winning_registration)。

plugin_source52–54 ↗
fn plugin_source(plugin_id: &str) -> McpServerSource

作用:把一个插件 ID 包装成“普通发现到的插件来源”。它主要用在断言里,方便检查冲突记录里写的是不是正确来源。

数据流:输入插件 ID → 先调用 plugin 生成插件归属信息 → 再包进 McpServerSource::Plugin → 输出一个来源对象。

调用关系:它是测试断言的小工具。source_precedence_preserves_the_winning_registration 和 earlier_plugin_wins_with_an_explicit_conflict 等测试会间接用它构造期望的冲突列表。

调用图:调用 1 个内部函数(plugin);外部调用 1 个(Plugin)。

selected_plugin_source56–58 ↗
fn selected_plugin_source(plugin_id: &str) -> McpServerSource

作用:把一个插件 ID 包装成“用户明确选中的插件来源”。这和普通扫描到的插件不同,测试要确认它有不同的优先级规则。

数据流:输入插件 ID → 调用 plugin 生成插件身份 → 包进 McpServerSource::SelectedPlugin → 输出一个来源对象。

调用关系:它主要服务于 selected_plugins_override_discovered_plugins_but_not_config 这类测试,用来写清楚期望结果:赢的是用户选中的插件,而不是普通发现到的插件。

调用图:调用 1 个内部函数(plugin);外部调用 1 个(SelectedPlugin)。

compatibility_source60–62 ↗
fn compatibility_source(id: &str) -> McpServerSource

作用:造一个“兼容旧格式”的服务器来源。测试用它表达:这个服务器不是新插件或扩展来的,而是为了兼容旧配置入口来的。

数据流:输入一个 ID → 把 ID 转成字符串并放进 McpServerSource::Compatibility → 输出一个来源对象。

调用关系:equal_precedence_uses_insertion_order_not_source_identity 会用它检查兼容来源之间冲突时的胜负和删除记录;source_precedence_preserves_the_winning_registration 也用它写期望冲突。

extension_source64–66 ↗
fn extension_source(id: &str) -> McpServerSource

作用:造一个“扩展贡献的服务器来源”。测试用它来对照最终目录里到底是不是扩展赢了。

数据流:输入扩展 ID → 把 ID 转成字符串并放进 McpServerSource::Extension → 输出一个来源对象。

调用关系:它常出现在断言里,尤其是验证扩展覆盖其他来源、或运行时扩展接管已有服务器时,期望结果会用它来描述最终来源。

register68–70 ↗
fn register(source: McpServerSource) -> McpServerConflictAction

作用:把一个来源包装成“注册动作”。冲突记录不只说谁参与了,还会说每个参与者是在注册还是删除,这个函数让断言更好读。

数据流:输入一个 McpServerSource → 包成 McpServerConflictAction::Register → 输出一个冲突动作对象。

调用关系:多个测试在检查 catalog.conflicts() 时会用它构造期望的 contenders 和 outcome。它只服务测试断言,不参与真正目录构建。

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

remove72–74 ↗
fn remove(source: McpServerSource) -> McpServerConflictAction

作用:把一个来源包装成“删除动作”。当测试要确认删除操作在冲突记录里压过注册操作时,会用到它。

数据流:输入一个 McpServerSource → 包成 McpServerConflictAction::Remove → 输出一个冲突动作对象。

调用关系:equal_precedence_uses_insertion_order_not_source_identity 在检查 remove_compatibility 的效果时使用它,说明最后的结果来自一次删除,而不是某个服务器注册。

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

source_precedence_preserves_the_winning_registration77–132 ↗
fn source_precedence_preserves_the_winning_registration()

作用:验证同名服务器来自多个地方时,目录会保留优先级规则选出的赢家,并且赢家的配置不会被输家偷偷改掉。

数据流:先造扩展、插件、兼容入口、配置文件等多个都叫 docs 的服务器 → 依次注册进构建器并构建目录 → 读取 docs 的最终服务器、插件归属表和冲突记录 → 断言最终来源是扩展、配置就是扩展那份、没有插件归属,并且冲突列表只记录相关插件挑战者。

调用关系:这是对整体优先级的核心测试。它把 server、plugin、from_extension、from_plugin、from_compatibility、from_config 和 builder 串起来,最后用断言检查 ResolvedMcpCatalog 的决策结果。

调用图:调用 7 个内部函数(from_compatibility, from_config, from_extension, from_plugin, builder, plugin, server);外部调用 2 个(assert!, assert_eq!)。

disabled_veto_only_disables_the_winning_registration135–156 ↗
fn disabled_veto_only_disables_the_winning_registration()

作用:验证显式禁用一个服务器名时,只会把最终赢出的那份服务器配置改成 disabled,而不是制造别的副作用。

数据流:先造一个扩展服务器,再准备一份期望结果并把 enabled 改成 false → 把扩展注册到构建器,然后调用 disable 禁用 docs → 构建目录并取出 docs 配置 → 输出通过/失败的测试结果,断言实际配置等于那份被禁用的期望配置。

调用关系:这个测试直接覆盖 builder.disable 的基本语义。它用 server 准备数据,用 from_extension 注册,再交给 ResolvedMcpCatalog 构建器算出最终目录。

调用图:调用 3 个内部函数(from_extension, builder, server);外部调用 1 个(assert_eq!)。

disabled_winner_remains_a_veto_when_the_catalog_is_extended159–186 ↗
fn disabled_winner_remains_a_veto_when_the_catalog_is_extended()

作用:验证一个已经禁用的赢家,在目录后来被继续扩展时,禁用状态还会像“否决票”一样传下去。

数据流:先注册一个来自配置文件、名字为 docs、但 enabled=false 的服务器 → 构建目录后再转回构建器 → 再注册一个同名扩展服务器 → 最终目录里 docs 应该来自扩展,但扩展配置的 enabled 被改成 false → 断言这个结果成立。

调用关系:它测试 build 后再 to_builder 继续加内容的场景。前半段用 from_config 制造一个禁用赢家,后半段用 from_extension 模拟运行时扩展覆盖,确认禁用信息没有丢。

调用图:调用 4 个内部函数(from_config, from_extension, builder, server);外部调用 1 个(assert_eq!)。

disabled_discovered_plugin_remains_a_veto_for_runtime_overlays189–218 ↗
fn disabled_discovered_plugin_remains_a_veto_for_runtime_overlays()

作用:验证普通发现到的插件如果贡献了一个被禁用的服务器,这个禁用状态会继续影响后来的运行时覆盖。

数据流:先造一个 enabled=false 的插件服务器并注册为 docs → 构建后转成新的构建器 → 再注册一个同名扩展服务器 → 构建最终目录 → 结果来源是扩展,但配置里的 enabled 仍然是 false。

调用关系:它和 disabled_winner_remains_a_veto_when_the_catalog_is_extended 很像,但来源换成普通插件。它调用 plugin、server 和 from_plugin、from_extension,检查普通插件的禁用也会成为后续覆盖的否决。

调用图:调用 5 个内部函数(from_extension, from_plugin, builder, plugin, server);外部调用 1 个(assert_eq!)。

earlier_plugin_wins_with_an_explicit_conflict221–253 ↗
fn earlier_plugin_wins_with_an_explicit_conflict()

作用:验证两个普通插件都注册同名服务器时,排在更前面的插件会赢,而且系统会明确记录这次冲突。

数据流:注册 alpha 插件的 docs,再注册 beta 插件的 docs → 构建目录 → 检查插件归属表里 docs 属于 alpha → 再检查冲突记录里 outcome 是 alpha 注册成功,contenders 里列出 beta 和 alpha 的注册动作。

调用关系:这个测试专门盯插件之间的竞争。它用 plugin 和 server 准备两个候选项,用 from_plugin 放入构建器,最后验证 ResolvedMcpCatalog 不但选对赢家,还留下可解释的冲突记录。

调用图:调用 4 个内部函数(from_plugin, builder, plugin, server);外部调用 1 个(assert_eq!)。

selected_plugins_override_discovered_plugins_but_not_config256–321 ↗
fn selected_plugins_override_discovered_plugins_but_not_config()

作用:验证“用户选中的插件”会压过普通发现到的插件,但如果用户配置文件里后来明确写了服务器,配置文件还能压过选中插件。

数据流:先注册一个普通发现插件的 docs,再注册两个用户选中的 docs 插件 → 构建后确认 selection_order 更早的 selected-alpha 赢,并记录插件归属和冲突 → 接着把目录转回构建器,注册一个配置文件来源的 docs → 再构建后确认最终赢家变成配置文件来源。

调用关系:这是选中插件优先级的综合测试。它先比较 from_plugin 和 from_selected_plugin,再加入 from_config,看目录构建器在两轮构建中是否都按规则重新决策。

调用图:调用 6 个内部函数(from_config, from_plugin, from_selected_plugin, builder, plugin, server);外部调用 1 个(assert_eq!)。

disabled_selected_plugin_does_not_veto_runtime_overlays324–352 ↗
fn disabled_selected_plugin_does_not_veto_runtime_overlays()

作用:验证被禁用的“用户选中插件”不会阻止后来的运行时扩展接管同名服务器。这和普通插件的禁用规则不一样,是一个容易出错的边界。

数据流:先注册一个 enabled=false 的用户选中插件服务器 → 构建后转回构建器 → 再注册一个同名扩展服务器 → 构建最终目录 → 断言 docs 来自扩展,并且扩展配置保持 enabled=true,没有被之前的禁用拖下水。

调用关系:它和 disabled_discovered_plugin_remains_a_veto_for_runtime_overlays 形成对照。两者都走 build 再 to_builder 再注册扩展的流程,但这个测试确认 selected plugin 的禁用不会变成运行时覆盖的否决票。

调用图:调用 5 个内部函数(from_extension, from_selected_plugin, builder, plugin, server);外部调用 1 个(assert_eq!)。

equal_precedence_uses_insertion_order_not_source_identity355–395 ↗
fn equal_precedence_uses_insertion_order_not_source_identity()

作用:验证同一优先级的来源冲突时,系统看的是加入顺序,而不是来源 ID 的字母顺序或长相。

数据流:先注册兼容来源 z-first,再注册兼容来源 a-second,二者都叫 docs → 构建后确认后加入的 a-second 赢 → 再把目录转回构建器并添加一次兼容来源删除动作 → 重新构建后 docs 消失,并且冲突记录显示删除动作成为最终结果。

调用关系:这个测试盯住兼容来源之间的平级竞争和删除覆盖。它用 from_compatibility 注册两个候选项,再用 remove_compatibility 加入删除动作,最后确认构建器按插入顺序和动作类型给出可解释结果。

调用图:调用 3 个内部函数(from_compatibility, builder, server);外部调用 1 个(assert_eq!)。

codex-mcp/src/connection_manager_tests.rs源码 ↗
testtest suite

MCP 可以理解成“外部工具服务器接口”,Codex 需要连接这些服务器、拿到工具列表、过滤工具、给模型展示合适的名字,还要在服务器没启动完时尽量用缓存顶上。这个测试文件就围绕这些容易出错的地方做检查:文件上传参数要被改成模型能理解的本地路径;工具名里有横杠、点号、超长名字时要安全改名但又不能丢失真实名字;启用/禁用工具的规则要按预期生效;Codex Apps 的工具缓存要按用户隔离、坏缓存要忽略、旧缓存要兼容;连接中的客户端如果还没准备好,有缓存就不阻塞,没缓存就等待,关机时还要能取消等待。最后它还检查登录失败和超时错误能显示给普通用户看得懂的提示。

函数细节51
create_test_tool47–64 ↗
fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo

作用:造一个假的工具对象,供测试使用。这样测试不用真的连外部 MCP 服务器,也能模拟“某个服务器提供了某个工具”。

数据流:输入服务器名和工具名 → 组装成一个 ToolInfo,里面带有工具描述、空的输入格式和默认的连接信息 → 返回这个可用于断言的假工具,不改动外部状态。

调用关系:它是很多测试的基础零件;例如文件参数展示测试和带连接器的工具测试会先用它造出工具,再交给被测函数继续处理。

调用图:被 4 处调用(codex_apps_server_info_cache_survives_legacy_tools_cache_write, create_test_tool_with_connector, tool_with_model_visible_input_schema_leaves_tools_without_file_params_unchanged, tool_with_model_visible_input_schema_masks_file_params);外部调用 5 个(new, default, new, format!, new)。

create_test_tool_with_connector66–76 ↗
fn create_test_tool_with_connector(
    server_name: &str,
    tool_name: &str,
    connector_id: &str,
    connector_name: Option<&str>,
) -> ToolInfo

作用:造一个带“连接器信息”的假工具。连接器可以理解成工具背后的具体应用或服务,比如日历、邮件等。

数据流:输入服务器名、工具名、连接器 ID 和可选显示名 → 先调用 create_test_tool 造普通工具,再补上连接器字段 → 返回带连接器身份的工具。

调用关系:它把造工具的重复活儿交给 create_test_tool,主要服务于检查缓存是否会过滤掉不允许的连接器。

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

create_codex_apps_tools_cache_context78–91 ↗
fn create_codex_apps_tools_cache_context(
    codex_home: PathBuf,
    account_id: Option<&str>,
    chatgpt_user_id: Option<&str>,
) -> CodexAppsToolsCacheContext

作用:造一个 Codex Apps 工具缓存的测试上下文。它告诉缓存代码:文件放哪里、属于哪个账号和用户。

数据流:输入临时的 Codex 主目录、账号 ID、ChatGPT 用户 ID → 包成 CodexAppsToolsCacheContext 和用户 key → 返回给缓存读写测试使用。

调用关系:缓存相关测试都会先用它准备“这个用户的缓存位置”,然后再调用读写缓存的函数验证隔离、覆盖、坏文件处理等行为。

调用图:被 8 处调用(codex_apps_server_info_cache_survives_legacy_tools_cache_write, codex_apps_tools_cache_filters_disallowed_connectors, codex_apps_tools_cache_is_ignored_when_json_is_invalid, codex_apps_tools_cache_is_ignored_when_schema_version_mismatches, codex_apps_tools_cache_is_overwritten_by_last_write, codex_apps_tools_cache_is_scoped_per_user, startup_cached_codex_apps_tools_loads_from_disk_cache, startup_cached_codex_apps_tools_loads_without_server_info_cache)。

create_test_server_info93–102 ↗
fn create_test_server_info(title: &str) -> McpServerInfo

作用:造一个假的 MCP 服务器信息对象。测试用它模拟服务器名称、标题和版本。

数据流:输入服务器标题 → 固定服务器名为 codex-apps,填入标题和版本号 → 返回 McpServerInfo,不访问网络。

调用关系:它给启动缓存和服务器信息缓存测试提供样本数据,让这些测试能检查缓存里保存和读出的服务器信息是否一致。

调用图:被 4 处调用(codex_apps_server_info_cache_survives_legacy_tools_cache_write, list_all_tools_uses_cached_tool_info_snapshot_when_client_startup_fails, list_available_server_infos_uses_cache_while_client_is_pending, startup_cached_codex_apps_tools_loads_from_disk_cache)。

model_tool_names104–109 ↗
fn model_tool_names(tools: &[ToolInfo]) -> HashSet<ToolName>

作用:把一组工具转换成模型真正会看到的工具名集合。这样测试可以只比较名字,不用关心整个工具对象的其他字段。

数据流:输入工具列表 → 遍历每个工具,取出它的标准工具名 → 输出一个去重后的名称集合。

调用关系:工具名规范化相关测试用它收集结果,再检查长名字、重名和命名空间冲突是否被正确处理。

调用图:被 2 处调用(test_normalize_tools_disambiguates_sanitized_namespace_collisions, test_normalize_tools_long_names_same_server);外部调用 1 个(iter)。

model_tool_name_len111–116 ↗
fn model_tool_name_len(name: &ToolName) -> usize

作用:计算一个模型可见工具名的长度。它把命名空间、分隔符和工具名本身都算进去。

数据流:输入一个 ToolName → 如果有命名空间,就加上命名空间长度和“__”分隔符长度,再加工具名长度 → 返回总长度数字。

调用关系:长工具名测试用它确认处理后的名字没有超过限制,保证模型侧能安全使用这些名字。

is_code_mode_compatible_tool_name118–125 ↗
fn is_code_mode_compatible_tool_name(name: &ToolName) -> bool

作用:检查工具名是否适合在代码模式里使用。简单说,就是名字里只能有英文字母、数字和下划线。

数据流:输入一个 ToolName → 把命名空间和工具名里的字符逐个检查 → 如果每个字符都安全就返回 true,否则返回 false。

调用关系:工具名清洗和冲突处理测试用它做最后验收,确认展示给模型的名字不会含点号、横杠等麻烦字符。

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

declared_openai_file_fields_treat_names_literally127–141 ↗
fn declared_openai_file_fields_treat_names_literally()

作用:确认文件参数名会被原样读取,不会被自动改写或猜测。这样服务器声明 file、input_file、attachments 时,系统就按这些名字处理。

数据流:输入一段带 openai/fileParams 的测试元数据 → 调用 declared_openai_file_input_param_names 解析 → 断言输出列表和声明顺序、内容完全一致。

调用关系:这是文件上传参数处理链条的入口校验,确保后面的 schema 改写拿到的是服务器明确声明的字段名。

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

tool_with_model_visible_input_schema_masks_file_params144–195 ↗
fn tool_with_model_visible_input_schema_masks_file_params()

作用:检查文件上传参数会被改成模型看得懂的“本地文件路径”。否则模型可能以为要直接构造复杂文件对象。

数据流:先造一个带 file 和 files 输入字段的工具,并在元数据里声明它们是文件参数 → 调用 tool_with_model_visible_input_schema → 断言这些字段展示给模型时变成字符串路径,描述里也加上上传文件该填绝对路径的说明。

调用关系:它使用 create_test_tool 准备样本,然后把核心验证交给 tool_with_model_visible_input_schema,确保 Codex Apps 的文件类工具对模型更友好。

调用图:调用 2 个内部函数(create_test_tool, tool_with_model_visible_input_schema);外部调用 4 个(new, assert_eq!, Meta, json!)。

tool_with_model_visible_input_schema_leaves_tools_without_file_params_unchanged198–204 ↗
fn tool_with_model_visible_input_schema_leaves_tools_without_file_params_unchanged()

作用:确认没有声明文件参数的工具不会被误改。这样普通工具的输入格式能保持原样。

数据流:输入一个普通假工具 → 调用 tool_with_model_visible_input_schema → 比较返回工具和原工具完全相同。

调用关系:它和文件参数遮罩测试形成一正一反:有声明才改,没声明就不碰。

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

elicitation_granular_policy_defaults_to_prompting207–226 ↗
fn elicitation_granular_policy_defaults_to_prompting()

作用:检查权限询问的默认策略不会随便拒绝 MCP 的询问。这里的 elicitation 指服务器向用户要补充信息或确认。

数据流:输入几种审批策略,包括失败时问、请求时问、非信任时问和细粒度配置 → 调用 elicitation_is_rejected_by_policy → 断言默认情况允许提示用户,只有细粒度里明确关闭 MCP 询问时才拒绝。

调用关系:它验证权限策略函数的默认方向,避免系统因为配置没写全就静默拒绝服务器的正常确认请求。

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

elicitation_granular_policy_respects_never_and_config229–240 ↗
fn elicitation_granular_policy_respects_never_and_config()

作用:确认“永不询问”和细粒度禁用配置会真的拒绝 MCP 询问。这样用户选择强限制时,系统不会偷偷弹出交互请求。

数据流:输入 AskForApproval::Never 和关闭 mcp_elicitations 的细粒度配置 → 调用策略判断 → 得到拒绝结果。

调用关系:它补上权限策略的强拒绝场景,和默认允许提示的测试一起保证边界清楚。

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

disabled_permissions_auto_accept_elicitation_with_empty_form_schema243–273 ↗
async fn disabled_permissions_auto_accept_elicitation_with_empty_form_schema()

作用:检查在权限被禁用时,如果服务器只是让用户确认、没有要求填写任何字段,系统可以自动接受。这样简单确认不会卡住流程。

数据流:创建一个 AskForApproval::Never 且 PermissionProfile::Disabled 的询问管理器 → 发出一个空表单的询问 → 得到 Accept 和空内容对象。

调用关系:测试通过 ElicitationRequestManager 生成 sender,再模拟服务器发请求,验证无字段确认的自动处理规则。

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

disabled_permissions_do_not_auto_accept_elicitation_with_requested_fields276–310 ↗
async fn disabled_permissions_do_not_auto_accept_elicitation_with_requested_fields()

作用:检查权限禁用时,如果服务器要求用户填写内容,系统不会自动编造答案,而是拒绝。这样不会把未知或敏感信息随便交给外部服务器。

数据流:创建禁用权限的询问管理器 → 发出一个要求填写 message 字段的表单 → 得到 Decline,内容为空。

调用关系:它和空表单自动接受测试配套,说明系统只会自动确认“无需输入”的请求,不会自动回答开放问题。

调用图:调用 1 个内部函数(new);外部调用 6 个(Number, assert_eq!, bounded, builder, String, new)。

test_normalize_tools_short_non_duplicated_names313–329 ↗
fn test_normalize_tools_short_non_duplicated_names()

作用:确认短且不重复的工具名会被正常加上 MCP 前缀并保留下来。这样模型看到的工具来源更清楚。

数据流:输入同一服务器上的两个普通工具 → 调用 normalize_tools_for_model_with_prefix → 输出带 mcp__server1 命名空间的两个工具名。

调用关系:这是工具名规范化的基本成功路径,后面的重名、长名和非法字符测试都以这个规则为基础。

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

test_normalize_tools_duplicated_names_skipped332–346 ↗
fn test_normalize_tools_duplicated_names_skipped()

作用:确认同一标准名字重复出现时,只保留第一个。这样模型不会看到两个名字一样、无法区分的工具。

数据流:输入两个服务器名和工具名都相同的工具 → 调用规范化函数 → 输出集合里只剩一个工具名。

调用关系:它验证 normalize_tools_for_model_with_prefix 的去重行为,防止工具列表里出现冲突项。

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

test_normalize_tools_long_names_same_server349–380 ↗
fn test_normalize_tools_long_names_same_server()

作用:检查超长工具名会被缩短到限制内,同时仍然能区分不同工具。这样不会超过模型或协议对名字长度的要求。

数据流:输入同一服务器上的两个很长工具名 → 调用规范化 → 收集模型名并计算长度 → 断言每个名字长度为 64、命名空间正确、字符也适合代码模式。

调用关系:它使用 model_tool_names 和 model_tool_name_len 做结果检查,验证规范化函数在长名字场景下既安全又不丢工具。

调用图:调用 2 个内部函数(model_tool_names, normalize_tools_for_model_with_prefix);外部调用 3 个(assert!, assert_eq!, vec!)。

test_normalize_tools_sanitizes_invalid_characters383–411 ↗
fn test_normalize_tools_sanitizes_invalid_characters()

作用:确认服务器名和工具名里的点号、横杠会被清洗成下划线。这样模型调用工具时不会因为名字含非法字符而失败。

数据流:输入 server.one 和 tool.two-three → 调用规范化 → 得到 mcp__server_one/tool_two_three,同时原始 MCP 工具名仍保存在 tool.tool.name 里。

调用关系:它验证一个重要折中:模型可见名字要安全,但真正发给 MCP 服务器调用时还要保留原始名字。

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

test_normalize_tools_keeps_hyphenated_mcp_tools_callable414–429 ↗
fn test_normalize_tools_keeps_hyphenated_mcp_tools_callable()

作用:确认带横杠的 MCP 工具仍然可调用。横杠对模型侧名字不友好,但很多真实服务器会这样命名工具。

数据流:输入 music-studio 的 get-strudel-guide → 规范化后模型看到 mcp__music_studio/get_strudel_guide → 原始工具名仍是 get-strudel-guide。

调用关系:它补充验证非法字符清洗不会破坏实际 MCP 调用,是工具名兼容性的现实案例。

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

test_normalize_tools_disambiguates_sanitized_namespace_collisions432–460 ↗
fn test_normalize_tools_disambiguates_sanitized_namespace_collisions()

作用:检查两个服务器名清洗后撞名时,系统会再做区分。比如 basic-server 和 basic_server 清洗后都像 basic_server,不能混在一起。

数据流:输入两个清洗后可能相同的服务器命名空间 → 调用规范化 → 断言输出里有两个不同命名空间,原始服务器名也都保留。

调用关系:它使用 model_tool_names 和代码模式检查,确保规范化函数既清洗字符,也处理清洗带来的新冲突。

调用图:调用 2 个内部函数(model_tool_names, normalize_tools_for_model_with_prefix);外部调用 3 个(assert!, assert_eq!, vec!)。

test_normalize_tools_disambiguates_sanitized_tool_name_collisions463–486 ↗
fn test_normalize_tools_disambiguates_sanitized_tool_name_collisions()

作用:检查两个工具名清洗后撞名时,系统会把它们区分开。否则 tool-name 和 tool_name 可能变成同一个可见名字。

数据流:输入同一服务器下两个容易撞名的工具 → 调用规范化 → 原始工具名都保留,模型可调用名数量为两个。

调用关系:它和命名空间冲突测试配套,覆盖服务器名冲突和工具名冲突两类问题。

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

tool_filter_allows_by_default489–493 ↗
fn tool_filter_allows_by_default()

作用:确认默认过滤器不会挡任何工具。没有配置启用或禁用列表时,工具应该都可用。

数据流:创建默认 ToolFilter → 查询任意工具名 → 返回允许。

调用关系:这是工具过滤规则的默认行为测试,为后面启用列表和禁用列表测试提供基线。

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

tool_filter_applies_enabled_list496–504 ↗
fn tool_filter_applies_enabled_list()

作用:确认如果配置了启用列表,只有列表里的工具能通过。它像白名单,只认写进去的名字。

数据流:创建 enabled 只包含 allowed 的过滤器 → 检查 allowed 和 denied → allowed 通过,denied 被拒绝。

调用关系:它验证 ToolFilter::allows 在白名单场景下的判断顺序。

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

tool_filter_applies_disabled_list507–515 ↗
fn tool_filter_applies_disabled_list()

作用:确认禁用列表会挡住指定工具。它像黑名单,没写进去的工具照常可用。

数据流:创建 disabled 包含 blocked 的过滤器 → 检查 blocked 和 open → blocked 被拒绝,open 通过。

调用关系:它验证 ToolFilter::allows 对黑名单的基础处理。

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

tool_filter_applies_enabled_then_disabled518–527 ↗
fn tool_filter_applies_enabled_then_disabled()

作用:确认启用列表和禁用列表同时存在时,禁用列表优先挡掉工具。也就是说,先得在白名单里,再不能在黑名单里。

数据流:创建 enabled 包含 keep 和 remove、disabled 包含 remove 的过滤器 → keep 通过,remove 和 unknown 都被拒绝。

调用关系:它把前两个过滤规则合在一起,确认最终优先级不会让被禁用工具漏出来。

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

filter_tools_applies_per_server_filters530–553 ↗
fn filter_tools_applies_per_server_filters()

作用:检查按服务器分别过滤工具是否正确。不同 MCP 服务器可以有不同的启用和禁用规则。

数据流:给 server1 和 server2 各准备工具和过滤器 → 分别调用 filter_tools 后合并结果 → 最后只剩 server1 的 tool_a。

调用关系:它验证单个 ToolFilter 的规则能通过 filter_tools 应用到实际工具列表上。

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

codex_apps_tools_cache_is_overwritten_by_last_write556–575 ↗
fn codex_apps_tools_cache_is_overwritten_by_last_write()

作用:确认同一个用户的 Codex Apps 工具缓存会被最后一次写入覆盖。这样缓存不会混用旧工具列表。

数据流:创建临时缓存位置 → 写入工具 one 并读回 → 再写入工具 two 并读回 → 最终读到 two。

调用关系:它使用缓存上下文创建函数和缓存读写函数,验证“写新覆盖旧”的基本磁盘缓存行为。

调用图:调用 3 个内部函数(read_cached_codex_apps_tools, write_cached_codex_apps_tools, create_codex_apps_tools_cache_context);外部调用 3 个(assert_eq!, tempdir, vec!)。

codex_apps_tools_cache_is_scoped_per_user578–608 ↗
fn codex_apps_tools_cache_is_scoped_per_user()

作用:确认不同用户的工具缓存互不影响。否则一个账号可能看到另一个账号可用的工具。

数据流:为两个用户创建不同缓存上下文 → 分别写入 one 和 two → 分别读回 → 两边内容不同,缓存文件路径也不同。

调用关系:它依赖 create_codex_apps_tools_cache_context 生成带用户身份的路径,验证缓存隔离这个安全边界。

调用图:调用 3 个内部函数(read_cached_codex_apps_tools, write_cached_codex_apps_tools, create_codex_apps_tools_cache_context);外部调用 4 个(assert_eq!, assert_ne!, tempdir, vec!)。

codex_apps_tools_cache_filters_disallowed_connectors611–639 ↗
fn codex_apps_tools_cache_filters_disallowed_connectors()

作用:确认写入或读取缓存时,不允许的连接器工具会被过滤掉。这样被封锁的外部应用不会因为缓存而重新出现。

数据流:准备一个 blocked_tool 和一个 allowed_tool → 写入缓存再读回 → 结果只剩 allowed_tool,连接器 ID 是 calendar。

调用关系:它使用 create_test_tool_with_connector 准备带连接器的工具,检查缓存层是否执行连接器过滤。

调用图:调用 3 个内部函数(read_cached_codex_apps_tools, write_cached_codex_apps_tools, create_codex_apps_tools_cache_context);外部调用 3 个(assert_eq!, tempdir, vec!)。

codex_apps_tools_cache_is_ignored_when_schema_version_mismatches642–661 ↗
fn codex_apps_tools_cache_is_ignored_when_schema_version_mismatches()

作用:确认缓存文件格式版本不匹配时会被忽略。这样新版程序不会误读旧版或未来版格式,避免解析出错或行为怪异。

数据流:手写一个 schema_version 比当前版本更高的缓存文件 → 调用 read_cached_codex_apps_tools → 得到 None,表示不使用这份缓存。

调用关系:它绕过正常写缓存函数,直接写磁盘坏版本文件,用来测试读缓存的防御能力。

调用图:调用 1 个内部函数(create_codex_apps_tools_cache_context);外部调用 6 个(assert!, json!, to_vec_pretty, create_dir_all, write, tempdir)。

codex_apps_tools_cache_is_ignored_when_json_is_invalid664–678 ↗
fn codex_apps_tools_cache_is_ignored_when_json_is_invalid()

作用:确认缓存文件不是合法 JSON 时会被忽略。JSON 是一种常见文本数据格式,坏掉时不能让程序崩溃。

数据流:在缓存路径写入“{not json”这样的坏内容 → 调用读缓存 → 返回 None。

调用关系:它测试缓存读取的容错逻辑,保证磁盘文件损坏时系统可以继续启动。

调用图:调用 1 个内部函数(create_codex_apps_tools_cache_context);外部调用 4 个(assert!, create_dir_all, write, tempdir)。

startup_cached_codex_apps_tools_loads_from_disk_cache681–714 ↗
fn startup_cached_codex_apps_tools_loads_from_disk_cache()

作用:确认启动阶段能从磁盘缓存拿到 Codex Apps 工具和服务器信息。这样真实服务器还没连上时,界面或模型也能先看到上次的工具列表。

数据流:创建缓存上下文、工具和服务器信息 → 调用 write_cached_codex_apps_tools_if_needed 写入 → 启动加载函数读回工具快照和服务器信息 → 断言内容一致。

调用关系:它串起缓存写入和启动读取两个阶段,验证启动加速用的缓存链路可用。

调用图:调用 5 个内部函数(load_startup_cached_codex_apps_server_info, load_startup_cached_codex_apps_tools_snapshot, write_cached_codex_apps_tools_if_needed, create_codex_apps_tools_cache_context, create_test_server_info);外部调用 3 个(assert_eq!, tempdir, vec!)。

startup_cached_codex_apps_tools_loads_without_server_info_cache717–748 ↗
fn startup_cached_codex_apps_tools_loads_without_server_info_cache()

作用:确认只有旧式工具缓存、没有服务器信息缓存时,启动仍能读出工具。这样升级后的程序还能兼容老用户的缓存文件。

数据流:手写只包含 schema_version 和 tools 的缓存文件 → 启动加载工具快照 → 成功得到工具;再读服务器信息 → 得到 None。

调用关系:它模拟旧缓存格式,保证 load_startup_cached_codex_apps_tools_snapshot 不会因为缺少新增字段而失败。

调用图:调用 3 个内部函数(load_startup_cached_codex_apps_server_info, load_startup_cached_codex_apps_tools_snapshot, create_codex_apps_tools_cache_context);外部调用 6 个(assert_eq!, json!, to_vec_pretty, create_dir_all, write, tempdir)。

codex_apps_server_info_cache_survives_legacy_tools_cache_write751–794 ↗
fn codex_apps_server_info_cache_survives_legacy_tools_cache_write()

作用:确认服务器信息缓存不会被旧格式工具缓存覆盖掉。这样老写法更新工具文件时,不会把旁边保存的服务器信息弄丢。

数据流:先用新写法写入工具和服务器信息 → 再手写一个低版本工具缓存覆盖工具文件 → 读取服务器信息仍成功,但启动工具快照因为版本旧而不可用。

调用关系:它同时调用新缓存写入、手写旧缓存和启动读取函数,验证服务器信息缓存和工具缓存的生命周期彼此独立。

调用图:调用 4 个内部函数(write_cached_codex_apps_tools_if_needed, create_codex_apps_tools_cache_context, create_test_server_info, create_test_tool);外部调用 7 个(assert!, assert_eq!, json!, to_vec_pretty, create_dir_all, write, tempdir)。

list_all_tools_uses_cached_tool_info_snapshot_while_client_is_pending797–834 ↗
async fn list_all_tools_uses_cached_tool_info_snapshot_while_client_is_pending()

作用:确认 MCP 客户端还在启动、一直没返回时,如果有工具快照缓存,list_all_tools 不会卡住。用户能先看到缓存里的工具。

数据流:创建一个永远 pending 的客户端 future,并放入一份启动工具快照 → 调用 manager.list_all_tools → 返回缓存里的 calendar_create_event 工具。

调用关系:它用 McpConnectionManager::new_uninitialized 搭一个测试管理器,验证工具列表查询在客户端未就绪时会走缓存兜底。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 6 个(new, new, assert_eq!, default, new, vec!)。

list_available_server_infos_uses_cache_while_client_is_pending837–871 ↗
async fn list_available_server_infos_uses_cache_while_client_is_pending()

作用:确认服务器还没启动完时,服务器信息也能从缓存读取,而且不会等待网络连接。这样界面展示服务器标题时不被慢启动拖住。

数据流:创建 pending 客户端和 cached_server_info → 用很短超时调用 list_available_server_infos → 成功立即返回缓存信息。

调用关系:它检查服务器信息查询路径和工具查询路径一样,有缓存时可以绕开未完成的客户端启动。

调用图:调用 4 个内部函数(new_uninitialized, create_test_server_info, allow_any, default);外部调用 8 个(new, new, from_millis, new, assert_eq!, default, new, timeout)。

list_all_tools_accepts_canonical_namespaced_tool_names874–914 ↗
async fn list_all_tools_accepts_canonical_namespaced_tool_names()

作用:确认不启用旧式 mcp__ 前缀时,工具会用标准的“服务器名 + 工具名”命名。这样新格式调用可以直接按命名空间解析。

数据流:创建 rmcp/echo 的缓存工具,管理器配置 prefix_mcp_tool_names 为 false → 调用 list_all_tools → 得到命名空间 rmcp、工具名 echo。

调用关系:它验证工具列表输出会尊重前缀配置,服务于新旧命名方式的兼容。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 6 个(new, new, assert_eq!, default, new, vec!)。

list_all_tools_applies_legacy_mcp_prefix_by_default917–957 ↗
async fn list_all_tools_applies_legacy_mcp_prefix_by_default()

作用:确认默认情况下仍会给 MCP 工具命名空间加 mcp__ 前缀。这样老版本或已有调用方式不会突然失效。

数据流:创建 rmcp/echo 缓存工具,管理器配置 prefix_mcp_tool_names 为 true → 调用 list_all_tools → 得到 mcp__rmcp/echo。

调用关系:它和不加前缀测试形成对照,确保 McpConnectionManager 根据配置选择正确命名规则。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 6 个(new, new, assert_eq!, default, new, vec!)。

list_all_tools_blocks_while_client_is_pending_without_cached_tool_info_snapshot960–986 ↗
async fn list_all_tools_blocks_while_client_is_pending_without_cached_tool_info_snapshot()

作用:确认客户端没启动完且没有工具缓存时,list_all_tools 会等待而不是编造结果。没有数据时宁可等,也不能给用户假工具。

数据流:创建 pending 客户端,但 cached_tool_info_snapshot 为 None → 用 10 毫秒超时调用 list_all_tools → 超时,说明它确实在等。

调用关系:它补上缓存兜底的反面场景:有缓存不阻塞,没缓存就等待真实客户端。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 7 个(new, new, from_millis, assert!, default, new, timeout)。

shutdown_cancels_pending_tool_listing989–1028 ↗
async fn shutdown_cancels_pending_tool_listing()

作用:确认关机时能取消正在等待的工具列表请求。否则程序退出时可能被一个永远启动不了的 MCP 客户端拖死。

数据流:创建一个会等取消信号才结束的客户端启动任务 → 启动 list_all_tools 等待它 → 调用 manager.shutdown → 启动任务被取消,工具列表返回空列表。

调用关系:它把取消令牌、后台任务和 shutdown 串起来,验证连接管理器的收尾逻辑能打断挂起的工具查询。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 10 个(clone, new, new, from_secs, assert!, default, new, spawn, channel, timeout)。

list_all_tools_does_not_block_when_cached_tool_info_snapshot_is_empty1031–1058 ↗
async fn list_all_tools_does_not_block_when_cached_tool_info_snapshot_is_empty()

作用:确认缓存快照即使是空列表,也算有效缓存,不应该阻塞。空缓存的意思是“已知没有工具”,不是“还不知道”。

数据流:创建 pending 客户端,并设置 cached_tool_info_snapshot 为 Some(Vec::new()) → 短超时调用 list_all_tools → 立即返回空工具列表。

调用关系:它细化缓存语义,防止代码把“有缓存但为空”和“没有缓存”混为一谈。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 8 个(new, new, from_millis, new, assert!, default, new, timeout)。

list_all_tools_uses_cached_tool_info_snapshot_when_client_startup_fails1061–1111 ↗
async fn list_all_tools_uses_cached_tool_info_snapshot_when_client_startup_fails()

作用:确认客户端启动失败后,如果有缓存,工具列表和服务器信息仍可用。这样临时网络或登录问题不会让上次可见信息全部消失。

数据流:创建一个立即失败的客户端 future,并放入工具快照和服务器信息缓存 → 调用 list_all_tools 和 list_available_server_infos → 返回缓存工具和缓存服务器信息。

调用关系:它验证缓存不仅用于“启动中”,也用于“启动失败”的降级场景。

调用图:调用 4 个内部函数(new_uninitialized, create_test_server_info, allow_any, default);外部调用 6 个(new, new, assert_eq!, default, new, vec!)。

list_all_tools_adds_server_metadata_to_cached_tools1114–1157 ↗
async fn list_all_tools_adds_server_metadata_to_cached_tools()

作用:确认从缓存拿到的工具也会补上服务器元数据。元数据包括来源地址、是否支持并行调用等,会影响后续怎么调用工具。

数据流:给 docs 服务器放入一份缓存工具和一份服务器元数据 → 调用 list_all_tools → 返回工具带有 supports_parallel_tool_calls 和 server_origin。

调用关系:它测试 McpConnectionManager 在缓存工具和配置元数据之间做合并,避免缓存工具缺少运行时需要的信息。

调用图:调用 3 个内部函数(new_uninitialized, allow_any, default);外部调用 9 个(new, new, new, assert!, assert_eq!, default, StreamableHttp, new, vec!)。

server_metadata_preserves_tool_approval_policy1160–1179 ↗
fn server_metadata_preserves_tool_approval_policy()

作用:确认服务器元数据会保留工具审批策略。审批策略决定工具调用前是自动批准、提示用户,还是按默认规则走。

数据流:构造一个 Codex Apps MCP 配置,默认工具审批为 Prompt,search 工具单独设为 Approve → 转成 McpServerMetadata → read 用默认 Prompt,search 用单独 Approve。

调用关系:它验证 EffectiveMcpServer 配置转换为 McpServerMetadata 时,不会丢掉按工具定制的审批设置。

调用图:调用 2 个内部函数(configured, from);外部调用 2 个(assert_eq!, codex_apps_mcp_server_config)。

no_local_runtime_fails_local_stdio_but_keeps_local_http_server1182–1293 ↗
async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server()

作用:确认没有本地运行环境时,本地 stdio MCP 服务器会失败,但本地 HTTP 服务器配置仍会保留。stdio 可以理解成通过本机命令启动的服务器,它需要本地环境。

数据流:创建一个 stdio 服务器和一个 HTTP 服务器,但运行上下文使用 without_environments → 初始化 McpConnectionManager → 两个客户端条目都存在;等待 stdio 就绪失败,并返回“需要本地环境”的错误。

调用关系:它覆盖连接管理器启动阶段的环境判断,确保缺少本地环境时只让不能运行的 stdio 失败,而不是把所有服务器都删掉。

调用图:调用 7 个内部函数(new, new, configured, allow_any, default, without_environments, default);外部调用 15 个(new, new, default, from, new, from, new, new, assert!, assert_eq! (+5 more))。

elicitation_capability_uses_2025_06_18_shape_for_form_only_support1296–1302 ↗
fn elicitation_capability_uses_2025_06_18_shape_for_form_only_support()

作用:确认只支持表单询问时,序列化出来的能力声明是空对象。序列化就是把程序里的对象变成 JSON 文本。

数据流:创建默认 ElicitationCapability → 转成 JSON → 得到 {}。

调用关系:它锁定与 2025-06-18 版本协议兼容的输出格式,防止能力声明形状被误改。

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

elicitation_capability_advertises_url_support_when_enabled1305–1317 ↗
fn elicitation_capability_advertises_url_support_when_enabled()

作用:确认启用 URL 询问能力时,能力声明里会明确写出 form 和 url。这样对方服务器能知道客户端支持打开链接式的询问。

数据流:创建同时带 form 和 url 的 ElicitationCapability → 转成 JSON → 得到包含 form:{} 和 url:{} 的对象。

调用关系:它和默认空对象测试配套,验证开启额外能力时协议输出会正确变化。

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

mcp_init_error_display_prompts_for_github_pat1320–1356 ↗
fn mcp_init_error_display_prompts_for_github_pat()

作用:确认 GitHub MCP 不支持 OAuth 时,会提示用户改用个人访问令牌。OAuth 是常见网页登录授权;个人访问令牌则是一串用户手动创建的密钥。

数据流:构造 GitHub MCP 配置、Unsupported 认证状态和 OAuth unsupported 错误 → 调用 mcp_init_error_display → 得到包含令牌创建地址和 config.toml 配置示例的提示。

调用关系:它验证启动失败展示逻辑对 GitHub 这个特殊服务有更贴心的说明,而不是只丢一个泛泛错误。

调用图:外部调用 4 个(new, anyhow!, assert_eq!, format!)。

mcp_init_error_display_prompts_for_login_when_auth_required1359–1370 ↗
fn mcp_init_error_display_prompts_for_login_when_auth_required()

作用:确认认证缺失时,会提示用户运行登录命令。这样用户知道下一步该做什么,而不是只看到“启动失败”。

数据流:输入服务器名 example 和“Auth required for server”错误 → 调用 mcp_init_error_display → 输出 codex mcp login example 的提示。

调用关系:它测试错误展示函数对“需要登录”这类常见错误的专门分支。

调用图:外部调用 3 个(anyhow!, assert_eq!, format!)。

mcp_init_error_display_reports_generic_errors1373–1407 ↗
fn mcp_init_error_display_reports_generic_errors()

作用:确认普通启动错误会按通用格式显示。不是所有错误都能给出专门建议,所以至少要清楚说哪个 MCP 客户端启动失败、失败原因是什么。

数据流:构造一个自定义 HTTP MCP 配置和 boom 错误 → 调用 mcp_init_error_display → 输出 MCP client for ... failed to start 加详细错误。

调用关系:它覆盖错误展示函数的兜底路径,和 GitHub、登录、超时这些特殊提示互补。

调用图:外部调用 4 个(new, anyhow!, assert_eq!, format!)。

mcp_init_error_display_includes_startup_timeout_hint1410–1420 ↗
fn mcp_init_error_display_includes_startup_timeout_hint()

作用:确认启动超时时,会告诉用户可以调大 startup_timeout_sec。这个配置控制等待 MCP 服务器启动的秒数。

数据流:输入 slow 服务器名和“request timed out”错误 → 调用 mcp_init_error_display → 输出 30 秒超时说明和 config.toml 配置示例。

调用关系:它验证错误展示函数能把超时这种可配置问题转成可操作的修复建议。

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

ext/mcp/tests/executor_plugin_mcp.rs源码 ↗
testtest execution

这份测试像一次“插件安检演练”。它先临时造出一个假的插件目录,里面放插件说明文件和 MCP 配置文件。MCP 可以理解成一种让程序连接外部工具或服务的接口。然后测试再造出一份企业下发的要求:只有某些服务器名和启动命令完全对得上,才算允许。测试把这个插件标记成当前选中的能力根目录,也就是“这次要检查的插件位置”。接着它启动扩展注册表,让 MCP 扩展去读取插件并产出服务器贡献项。最后它检查结果:命令匹配的服务器启用,命令不匹配或没列入企业要求的服务器禁用。这样可以保证托管策略不会被插件本地配置绕过。

函数细节2
selected_plugin_servers_use_managed_requirements_for_the_selected_root_id27–91 ↗
async fn selected_plugin_servers_use_managed_requirements_for_the_selected_root_id() -> TestResult

作用:这是主测试用例,用来验证“选中的插件服务器”会按企业托管要求来决定是否启用。它特别检查三种情况:允许的、名字存在但命令不一致的、完全没被列入要求的。

数据流:进去时没有外部输入,测试自己创建临时的 Codex 主目录和插件目录。它往插件目录写入插件清单和 .mcp.json,再用 CloudConfigBundleFixture::loader_with_enterprise_requirement 准备一份企业要求配置,然后构建 Config。之后它把这些交给 selected_plugin_contributions,拿回插件服务器的摘要列表。最后用 assert_eq! 对比期望结果:allowed 是启用,mismatched 和 unlisted 都是禁用;测试通过则返回 Ok,失败就报错。

调用关系:它是这份文件的测试入口,由测试框架 tokio 自动运行。它负责搭好测试现场,包括调用临时目录、写文件、构建配置,然后把真正收集贡献项的工作交给 selected_plugin_contributions。拿到结果后,它只做最后的验收判断。

调用图:调用 2 个内部函数(loader_with_enterprise_requirement, selected_plugin_contributions);外部调用 5 个(assert_eq!, default, create_dir_all, write, tempdir)。

selected_plugin_contributions93–139 ↗
async fn selected_plugin_contributions(
    config: &Config,
    plugin_root: &std::path::Path,
) -> Vec<ContributionSummary>

作用:这个辅助函数负责把一个插件目录和配置送进 MCP 扩展系统,然后取回这个插件声明出来的 MCP 服务器列表。它把复杂的扩展注册、线程数据初始化和结果转换包装起来,让测试主体更好读。

数据流:进去的是 Config 和插件根目录路径。函数先新建 ExtensionRegistryBuilder,把 executor 插件能力安装进去,并使用测试用的 EnvironmentManager。接着它创建 ExtensionDataInit,把一个 id 为 selected-root 的已选能力根目录塞进去,位置指向传入的插件目录,并初始化 executor 插件线程数据。然后它从注册表里取第一个 MCP 服务器贡献者,用 McpServerContributionContext::for_thread 生成上下文并异步请求贡献结果。最后它只接受 SelectedPlugin 类型,把每个贡献项整理成 ContributionSummary,输出一个摘要列表;如果出现 Set 或 Remove 类型,就直接 panic,因为这不是本测试预期的结果。

调用关系:它被 selected_plugin_servers_use_managed_requirements_for_the_selected_root_id 调用,扮演“跑一遍扩展系统并整理结果”的帮手。它内部把扩展注册交给 install_executor_plugins,把线程数据准备交给 initialize_executor_plugin_thread_data,把贡献请求交给注册表中的 MCP 贡献者,最后把底层返回值变成测试容易比较的简短结构。

调用图:调用 4 个内部函数(default_for_tests, for_thread, new, new);被 1 处调用(selected_plugin_servers_use_managed_requirements_for_the_selected_root_id);外部调用 4 个(new, initialize_executor_plugin_thread_data, install_executor_plugins, vec!)。

ext/mcp/tests/hosted_apps_mcp.rs源码 ↗
testtest

这里测试的是 Apps MCP。MCP 可以理解成一种让 Codex 连接外部工具或服务的通道;Apps MCP 就是 Codex 内置的、给 ChatGPT Apps/插件运行时用的那条通道。这个文件不是真正运行服务,而是搭出临时配置、假登录信息和扩展注册表,然后问 McpManager:“最终应该有哪些 MCP 服务器?”测试会检查几件关键事:打开 apps 功能并且是 ChatGPT 登录时,会自动出现正确的 hosted Apps MCP 地址;用户把同名服务器禁用时,禁用状态不能被偷偷改回来;没有新扩展时,旧的兜底逻辑还会把保留名称指向旧地址;后安装的扩展可以删除同名注册;只有 API key 登录时不能启用 hosted Apps MCP;关闭 apps 功能时,同名保留配置也要被清掉。最后还定义了一个小测试扩展 RemoveCodexApps,专门模拟“后来的扩展把内置 Apps MCP 删除”的情况。

函数细节9
contributes_hosted_plugin_runtime_without_an_executor19–44 ↗
async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult

作用:这个测试确认:只要打开 apps 功能、使用 ChatGPT 登录,并安装了 MCP 扩展,系统就会自动贡献一个 hosted Apps MCP 服务器。它还检查这个服务器使用的是正确的 ChatGPT 后端地址。

数据流:进去的是一个临时目录、测试用配置和假的 ChatGPT 登录信息。测试用 installed_manager 建好带扩展的 McpManager,再向它询问最终有效的服务器列表。出来的结果应该包含名为 codex_apps 的服务器,并且它的传输方式是可流式 HTTP,也就是通过网页地址持续通信;最后断言网址是 https://chatgpt.com/backend-api/ps/mcp。

调用关系:它代表最正常的成功路径:测试先调用 installed_manager 搭好带 codex_mcp_extension 的管理器,再调用登录测试工具生成 ChatGPT 身份,最后检查 McpManager 算出的服务器结果。这个测试保证扩展安装后真的会把 hosted Apps MCP 接进系统。

调用图:调用 2 个内部函数(installed_manager, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(assert_eq!, default, panic!, tempdir, vec!)。

runtime_overlay_preserves_disabled_server47–72 ↗
async fn runtime_overlay_preserves_disabled_server() -> TestResult

作用:这个测试确认:如果用户已经把 codex_apps 这个 MCP 服务器设成禁用,运行时自动加上的配置不能把它重新打开。简单说,用户明确关掉的东西,系统不能自作主张打开。

数据流:进去的是一份临时配置,其中 apps 功能打开,同时用户配置了 mcp_servers.codex_apps.url,并把 enabled 设为 false。测试创建 ChatGPT 假登录和带扩展的管理器,然后拿到最终服务器列表。出来的结果里 codex_apps 还在,但它的 enabled 状态必须是 false。

调用关系:它调用 installed_manager 走扩展安装路径,然后让 McpManager 合并用户配置和运行时扩展贡献。这个测试重点盯住“合并配置”这一步,防止扩展覆盖用户的禁用选择。

调用图:调用 2 个内部函数(installed_manager, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(assert!, default, tempdir, vec!)。

legacy_fallback_overwrites_reserved_config_without_an_extension75–105 ↗
async fn legacy_fallback_overwrites_reserved_config_without_an_extension() -> TestResult

作用:这个测试确认:如果没有安装新的 MCP 扩展,旧的兜底逻辑仍然会接管 codex_apps 这个保留名称,并把它指向旧版 Apps MCP 地址。它是在保护老路径不被新机制意外破坏。

数据流:进去的是一份打开 apps 功能、并且用户给 codex_apps 配了 example.com 地址的配置,以及假的 ChatGPT 登录。测试故意不用 installed_manager,而是直接创建没有扩展注册表的 McpManager。出来的服务器配置应该仍然有 codex_apps,但地址被旧兜底逻辑改成 https://chatgpt.com/backend-api/wham/apps。

调用关系:它绕开 installed_manager,直接用 McpManager::new 和 PluginsManager::new,模拟“没有扩展参与”的旧环境。这个测试和 hosted 扩展路径形成对照:有扩展时用新地址,没有扩展时旧兜底仍然工作。

调用图:调用 3 个内部函数(new, new, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(new, assert_eq!, default, panic!, tempdir, vec!)。

later_extension_can_remove_same_name_registration108–129 ↗
async fn later_extension_can_remove_same_name_registration() -> TestResult

作用:这个测试确认:如果后注册的扩展声明要移除 codex_apps,同名的 Apps MCP 注册就真的会被删掉。它检查扩展之间的覆盖顺序是否符合预期。

数据流:进去的是打开 apps 功能的临时配置和假的 ChatGPT 登录。测试先把正常的 codex_mcp_extension 装进扩展注册表,再额外注册 RemoveCodexApps 这个测试扩展。出来的最终服务器列表里不应该再有 codex_apps。

调用关系:它手动创建 ExtensionRegistryBuilder,先调用 codex_mcp_extension::install 加入正常贡献者,再加入 RemoveCodexApps。随后通过 McpManager::new_with_extensions 运行整套扩展贡献流程,用结果证明后来的移除贡献能压过前面的同名注册。

调用图:调用 4 个内部函数(new, new_with_extensions, new, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(new, assert!, install, default, tempdir, vec!)。

hosted_apps_mcp_requires_chatgpt_auth132–147 ↗
async fn hosted_apps_mcp_requires_chatgpt_auth() -> TestResult

作用:这个测试确认 hosted Apps MCP 只允许在 ChatGPT 登录身份下出现,单纯 API key 登录不行。这样可以避免把需要 ChatGPT 会话的服务错误暴露给不合适的登录方式。

数据流:进去的是打开 apps 功能的临时配置,以及用 from_api_key 造出的 API key 登录信息。测试用 installed_manager 创建带扩展的管理器,再计算有效服务器列表。出来的结果不应该包含 codex_apps。

调用关系:它和第一个成功路径测试形成对比:同样打开 apps 功能、同样安装扩展,但登录类型从 ChatGPT 假登录换成 API key。这个测试说明扩展贡献不只看功能开关,还会看认证方式。

调用图:调用 2 个内部函数(installed_manager, from_api_key);外部调用 4 个(assert!, default, tempdir, vec!)。

disabled_apps_remove_reserved_server_config_for_all_hosts150–175 ↗
async fn disabled_apps_remove_reserved_server_config_for_all_hosts() -> TestResult

作用:这个测试确认:只要 apps 功能被关闭,codex_apps 这个保留服务器配置就应该被移除,不管走新扩展路径还是旧兜底路径。它防止用户关掉功能后,系统还残留一条看起来能用的内置服务器。

数据流:进去的是一份把 features.apps 设为 false、同时又配置了 mcp_servers.codex_apps.url 的临时配置。测试准备两个管理器:一个带扩展,一个不带扩展。对每个管理器都读取运行时服务器列表,出来的结果都不能包含 codex_apps。

调用关系:它同时调用 installed_manager 和直接创建的 McpManager,覆盖新旧两种宿主情况。这个测试把同一个关闭功能的规则套到所有路径上,确保行为一致。

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

installed_manager177–184 ↗
fn installed_manager(config: &Config) -> McpManager

作用:这个辅助函数专门给测试创建一个“已经安装好 Codex MCP 扩展”的 McpManager。这样多个测试不用重复写同一段装配代码。

数据流:进去的是 Config,主要读取其中的 codex_home 路径。函数新建扩展注册表,把 codex_mcp_extension 安装进去,再用这个注册表和 PluginsManager 创建 McpManager。出来的是一个已经具备扩展贡献能力的管理器。

调用关系:它被多个测试调用,用来搭建标准的“新扩展已安装”场景。内部把活交给 ExtensionRegistryBuilder、codex_mcp_extension::install、PluginsManager::new 和 McpManager::new_with_extensions,等于把测试前的布景工作封装起来。

调用图:调用 3 个内部函数(new, new_with_extensions, new);被 4 处调用(contributes_hosted_plugin_runtime_without_an_executor, disabled_apps_remove_reserved_server_config_for_all_hosts, hosted_apps_mcp_requires_chatgpt_auth, runtime_overlay_preserves_disabled_server);外部调用 2 个(new, install)。

RemoveCodexApps::id189–191 ↗
fn id(&self) -> &'static str

作用:这个函数给测试扩展 RemoveCodexApps 返回一个固定名字。这个名字只是用来标识这个贡献者是谁,方便系统区分不同扩展。

数据流:进去的是 RemoveCodexApps 自身,没有读取外部配置。函数直接返回字符串 remove_codex_apps。它不修改任何状态。

调用关系:它是 McpServerContributor 这个接口的一部分;当扩展系统识别贡献者时会用到这个 ID。它配合 RemoveCodexApps::contribute 一起构成一个完整的测试扩展。

RemoveCodexApps::contribute193–202 ↗
fn contribute(
        &'a self,
        _context: McpServerContributionContext<'a, Config>,
    ) -> codex_extension_api::ExtensionFuture<'a, Vec<McpServerContribution>>

作用:这个函数模拟一个扩展说:“请把名为 codex_apps 的 MCP 服务器移除。”它用于测试后来的扩展能不能删除前面已经注册的同名服务器。

数据流:进去的是扩展贡献上下文,但这个测试实现没有使用里面的信息。函数返回一个异步结果,结果里只有一条 McpServerContribution::Remove,目标名称是 CODEX_APPS_MCP_SERVER_NAME。出来的效果是告诉 MCP 管理流程删除这个名字对应的服务器贡献。

调用关系:它在 later_extension_can_remove_same_name_registration 这个测试搭出的扩展注册表里被调用。正常扩展先贡献 codex_apps,RemoveCodexApps::contribute 随后给出删除指令,最终 McpManager 合并贡献时就应该把 codex_apps 从结果中拿掉。

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

core/tests/suite/rmcp_client.rs源码 ↗
testintegration test execution

MCP 可以理解成“外接工具插座”:模型想调用某个工具时,Codex 要把请求送到 MCP 服务器,再把结果交回模型。这个测试文件就是给这套插座做全流程体检。它会启动假的模型接口、启动测试用 MCP 服务器,然后让 Codex 像真实运行一样发起一轮对话。测试覆盖两种连接方式:stdio(通过子进程标准输入输出通信)和 Streamable HTTP(通过 HTTP 通信)。它还特别检查远程 Docker 环境、工作目录选择、环境变量白名单、沙箱信息、图片结果、文本模型不能收图时的降级、并发工具调用规则,以及 OAuth 令牌。没有这些测试,MCP 相关改动很容易只在本机小例子里能跑,到了远程执行、鉴权、图片或多工具并发时才出问题。

函数细节51
assert_wall_time_line81–83 ↗
fn assert_wall_time_line(line: &str)

作用:检查一行文字是不是形如“Wall time: 1.23 seconds”的耗时说明。测试用它确认工具输出里带了人能看懂的运行时间。

数据流:输入是一行字符串 → 它用正则表达式匹配固定格式 → 如果格式不对就让测试失败,不返回业务数据。

调用关系:它是输出校验的小积木,被 assert_wall_time_header 和 split_wall_time_wrapped_output 调用,先把耗时行把关住,再让后面的测试继续检查真正的工具内容。

调用图:被 2 处调用(assert_wall_time_header, split_wall_time_wrapped_output);外部调用 1 个(assert_regex_match)。

split_wall_time_wrapped_output85–92 ↗
fn split_wall_time_wrapped_output(output: &str) -> &str

作用:把带“运行耗时头”的工具输出拆开,只留下真正的输出内容。这样测试不用每次手动跳过第一行。

数据流:输入是一整段输出文本 → 它先切出第一行并交给 assert_wall_time_line 检查 → 再确认后面有“Output:”标记,最后返回标记之后的内容。

调用关系:多个 MCP 工具调用测试都会用它读取返回给模型的 JSON 内容;它依赖 assert_wall_time_line 保证前面的耗时格式没有悄悄变掉。

调用图:调用 1 个内部函数(assert_wall_time_line);被 6 处调用(stdio_image_responses_are_sanitized_for_text_only_model, stdio_mcp_parallel_tool_calls_default_false_runs_serially, stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently, stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in, stdio_mcp_tool_call_includes_sandbox_state_meta, stdio_server_round_trip)。

assert_wall_time_header94–100 ↗
fn assert_wall_time_header(output: &str)

作用:检查多段内容里的第一项是不是只有耗时头和“Output:”标记。图片类输出会把耗时作为一个单独文本项,所以需要这种检查。

数据流:输入是一段文本 → 它拆成耗时行和下一行标记 → 检查耗时行格式正确,并检查第二行正好是“Output:”。

调用关系:图片 MCP 测试调用它,确保发送给模型的图片列表前面仍然保留运行时间说明;它把具体耗时行检查交给 assert_wall_time_line。

调用图:调用 1 个内部函数(assert_wall_time_line);被 2 处调用(stdio_image_responses_preserve_original_detail_metadata, stdio_image_responses_round_trip);外部调用 1 个(assert_eq!)。

read_only_user_turn102–104 ↗
fn read_only_user_turn(fixture: &TestCodex, text: impl Into<String>) -> Op

作用:快速造一个“只读权限”的用户请求。测试用它模拟用户让模型调用不会修改环境的工具。

数据流:输入是测试夹具和用户文字 → 它读取当前会话里的模型名 → 交给 read_only_user_turn_with_model 生成完整的 Op 请求。

调用关系:很多测试提交用户轮次前都会用它;它本身不拼所有字段,而是把模型和权限细节交给 read_only_user_turn_with_model。

调用图:调用 1 个内部函数(read_only_user_turn_with_model);被 11 处调用(call_cwd_tool, remote_stdio_env_var_source_does_not_copy_local_env, stdio_image_responses_preserve_original_detail_metadata, stdio_image_responses_resize_large_image, stdio_image_responses_round_trip, stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in, stdio_server_propagates_explicit_local_env_var_source, stdio_server_propagates_whitelisted_env_vars, stdio_server_round_trip, streamable_http_tool_call_round_trip (+1 more))。

read_only_user_turn_with_model106–112 ↗
fn read_only_user_turn_with_model(
    fixture: &TestCodex,
    text: impl Into<String>,
    model: String,
) -> Op

作用:造一个指定模型的只读用户请求。它用于测试某个模型能力不同,比如文本模型不支持图片输入时的行为。

数据流:输入是测试夹具、用户文字和模型名 → 它选择只读权限配置 → 调用 user_turn_with_permission_profile 生成最终请求。

调用关系:read_only_user_turn 会调用它作为默认版本;文本模型图片降级测试会直接调用它,以便指定一个只支持文字的模型。

调用图:调用 2 个内部函数(user_turn_with_permission_profile, read_only);被 2 处调用(read_only_user_turn, stdio_image_responses_are_sanitized_for_text_only_model)。

auto_approved_user_turn114–121 ↗
fn auto_approved_user_turn(fixture: &TestCodex, text: impl Into<String>) -> Op

作用:造一个不需要人工批准的用户请求。测试并发调度时用它避开审批流程干扰。

数据流:输入是测试夹具和用户文字 → 它使用当前模型名,并把权限设为 Disabled,也就是不走沙箱审批限制 → 输出一个可提交给 Codex 的 Op。

调用关系:并发工具调用的测试会用它,让测试重点放在“两个工具是串行还是并行”,而不是被权限确认卡住。

调用图:调用 1 个内部函数(user_turn_with_permission_profile);被 2 处调用(stdio_mcp_parallel_tool_calls_default_false_runs_serially, stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently)。

user_turn_with_permission_profile123–155 ↗
fn user_turn_with_permission_profile(
    fixture: &TestCodex,
    text: impl Into<String>,
    model: String,
    permission_profile: PermissionProfile,
) -> Op

作用:这是构造用户请求的底层函数。它把文本、模型、权限和沙箱设置打包成 Codex 能处理的一次用户输入。

数据流:输入是测试夹具、文字、模型名和权限档位 → 它根据当前工作目录算出沙箱策略和权限字段 → 输出一个 Op::UserInput,并附带线程设置覆盖项。

调用关系:read_only_user_turn_with_model 和 auto_approved_user_turn 都把最终组装工作交给它;它是这些测试里“模拟用户说话”的共同入口。

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

remote_aware_environment_id165–171 ↗
fn remote_aware_environment_id() -> String

作用:根据当前测试是不是跑在远程环境,选择 MCP 服务器该使用的环境编号。这样同一套测试可以本地跑,也可以在远程执行器里跑。

数据流:它读取 test_environment 的状态 → 如果是远程测试,就返回 remote → 否则返回默认 MCP 环境编号。

调用关系:配置 MCP 服务器时经常会用到它,让后续 Codex fixture 知道该把工具服务器放在哪个执行环境里。

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

remote_aware_stdio_server_bin180–197 ↗
fn remote_aware_stdio_server_bin() -> anyhow::Result<String>

作用:找到 stdio MCP 测试服务器的可执行文件路径,并在远程测试时把它复制进 Docker 容器。否则远程进程看不到主机上的二进制文件。

数据流:它先拿到本机编译出的测试服务器路径 → 检查是否有 Docker 容器名 → 本地时直接返回路径,远程时调用 copy_binary_to_remote_env 复制后返回容器内路径。

调用关系:绝大多数 stdio MCP 测试在配置服务器命令时会调用它;它把具体 Docker 复制工作交给 copy_binary_to_remote_env。

调用图:调用 1 个内部函数(copy_binary_to_remote_env);被 13 处调用(remote_stdio_env_var_source_does_not_copy_local_env, stdio_image_responses_are_sanitized_for_text_only_model, stdio_image_responses_preserve_original_detail_metadata, stdio_image_responses_resize_large_image, stdio_image_responses_round_trip, stdio_mcp_parallel_tool_calls_default_false_runs_serially, stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently, stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in, stdio_mcp_tool_call_includes_sandbox_state_meta, stdio_server_propagates_explicit_local_env_var_source (+3 more));外部调用 3 个(new, stdio_server_bin, test_environment)。

unique_remote_path200–206 ↗
fn unique_remote_path(binary_name: &str) -> anyhow::Result<String>

作用:给复制到远程容器里的测试二进制生成一个不容易撞名的路径。这样并行测试不会互相覆盖文件。

数据流:输入是二进制文件的短名字 → 它读取当前时间纳秒和当前进程号 → 输出一个 /tmp/codex-remote-env 下的唯一路径字符串。

调用关系:copy_binary_to_remote_env 在复制文件前调用它,先决定目标地址,再执行 Docker mkdir、cp 和 chmod。

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

copy_binary_to_remote_env209–263 ↗
fn copy_binary_to_remote_env(
    container_name: &str,
    host_path: &Path,
    binary_name: &str,
) -> anyhow::Result<String>

作用:把主机上编译好的测试辅助程序复制到远程 Docker 容器,并设成可执行。远程 MCP 测试能不能启动工具服务器就靠它铺路。

数据流:输入是容器名、主机文件路径和二进制名字 → 它生成远程路径,创建目录,docker cp 复制文件,再 chmod 加执行权限 → 输出容器内可运行的路径;失败时返回带上下文的错误。

调用关系:remote_aware_stdio_server_bin 用它复制 stdio 测试服务器;start_remote_streamable_http_test_server 用它复制 HTTP 测试服务器。

调用图:调用 1 个内部函数(unique_remote_path);被 2 处调用(remote_aware_stdio_server_bin, start_remote_streamable_http_test_server);外部调用 3 个(new, ensure!, format!)。

TestMcpServerOptions::default272–278 ↗
fn default() -> Self

作用:给测试 MCP 服务器配置一套默认选项。默认是不支持并发工具调用、没有工具超时、使用默认环境。

数据流:没有输入 → 构造 TestMcpServerOptions → 输出带默认 environment_id、supports_parallel_tool_calls=false、tool_timeout_sec=None 的值。

调用关系:很多测试只关心传输方式,不关心额外选项,就用这个默认值;需要并发或超时时再覆盖字段。

stdio_transport281–287 ↗
fn stdio_transport(
    command: String,
    env: Option<HashMap<String, String>>,
    env_vars: Vec<McpServerEnvVar>,
) -> McpServerTransportConfig

作用:快速创建 stdio 方式的 MCP 传输配置。stdio 就是通过子进程的标准输入输出跟工具服务器说话。

数据流:输入是启动命令、可选环境变量表、允许传入的环境变量列表 → 它把工作目录设为空 → 调用 stdio_transport_with_cwd 输出完整配置。

调用关系:大多数 stdio 测试用它简化配置;如果测试要指定工作目录,就改用 stdio_transport_with_cwd。

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

stdio_transport_with_cwd289–302 ↗
fn stdio_transport_with_cwd(
    command: String,
    env: Option<HashMap<String, String>>,
    env_vars: Vec<McpServerEnvVar>,
    cwd: Option<PathBuf>,
) -> McpServerTransportConfig

作用:创建带工作目录选项的 stdio MCP 传输配置。它让测试能检查工具服务器到底从哪个目录启动。

数据流:输入是命令、环境变量、环境变量白名单和可选工作目录 → 它组装成 McpServerTransportConfig::Stdio → 输出给 insert_mcp_server 使用。

调用关系:stdio_transport 会调用它作为无工作目录的简化版;工作目录优先级测试会直接用它传入 cwd。

调用图:被 1 处调用(stdio_transport);外部调用 1 个(new)。

insert_mcp_server304–335 ↗
fn insert_mcp_server(
    config: &mut Config,
    server_name: &str,
    transport: McpServerTransportConfig,
    options: TestMcpServerOptions,
)

作用:把一个 MCP 服务器塞进测试用的 Codex 配置里。没有它,测试夹具启动时就不知道有哪些外接工具服务器。

数据流:输入是可修改配置、服务器名、传输配置和测试选项 → 它复制现有服务器表,插入一条启用的 McpServerConfig,再写回配置 → 配置对象被改动。

调用关系:各个测试在 test_codex 的配置回调里调用它;后续 Codex 启动时会按这份配置连接 MCP 服务器。

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

call_cwd_tool337–389 ↗
async fn call_cwd_tool(
    server: &MockServer,
    fixture: &TestCodex,
    server_name: &str,
    call_id: &str,
) -> anyhow::Result<Value>

作用:驱动一次 cwd 工具调用,并取回工具报告的当前工作目录。cwd 工具就是告诉你服务器进程现在在哪个目录下运行。

数据流:输入是假模型服务器、测试夹具、MCP 服务器名和调用 ID → 它挂载两段假的模型流,提交只读用户请求,等待工具开始和结束 → 输出工具返回的 structured_content JSON。

调用关系:工作目录相关测试调用它复用完整调用流程;它内部用 read_only_user_turn 造请求,用 wait_for_event 等待 MCP 事件。

调用图:调用 3 个内部函数(mount_sse_once, sse, read_only_user_turn);被 2 处调用(local_stdio_server_uses_runtime_fallback_cwd_when_config_omits_cwd, stdio_server_uses_configured_cwd_before_runtime_fallback);外部调用 4 个(wait_for_event, format!, unreachable!, vec!)。

assert_cwd_tool_output391–417 ↗
fn assert_cwd_tool_output(structured: &Value, expected_cwd: &Path)

作用:检查 cwd 工具返回的目录是不是期望目录。它还照顾本地 Windows 可能出现短路径的问题。

数据流:输入是工具返回的 JSON 和期望路径 → 它取出 cwd 字符串 → 远程时直接比字符串,本地时转成规范路径再比较;不匹配就让测试失败。

调用关系:配置 cwd 优先和运行时 fallback cwd 两个测试都会在 call_cwd_tool 后调用它,专门验证目录选择规则。

调用图:被 2 处调用(local_stdio_server_uses_runtime_fallback_cwd_when_config_omits_cwd, stdio_server_uses_configured_cwd_before_runtime_fallback);外部调用 3 个(get, assert_eq!, test_environment)。

stdio_server_round_trip421–559 ↗
async fn stdio_server_round_trip() -> anyhow::Result<()>

作用:测试最基本的 stdio MCP 来回调用:模型要求调用 echo 工具,Codex 调工具,结果再返回给模型。它还确认环境变量能传到工具服务器。

数据流:它启动假模型服务器,挂好模型会发出的工具调用,配置 stdio MCP 服务器和环境变量 → 提交用户请求并等待 MCP 开始、结束、整轮完成 → 检查工具结果、回传给模型的 JSON、命名空间工具声明和 mock 请求都正确。

调用关系:这是 stdio MCP 的主干冒烟测试;它用 remote_aware_stdio_server_bin 适配远程环境,用 split_wall_time_wrapped_output 检查返回给模型的包裹输出。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, remote_aware_stdio_server_bin, split_wall_time_wrapped_output);外部调用 10 个(assert!, assert_eq!, wait_for_event, wait_for_mcp_server, format!, from_str, skip_if_no_network!, skip_if_wine_exec!, unreachable!, vec!)。

shutdown_cancels_startup_prewarm_waiting_for_mcp_startup562–605 ↗
async fn shutdown_cancels_startup_prewarm_waiting_for_mcp_startup() -> anyhow::Result<()>

作用:测试 Codex 关闭时不会被正在预热启动的 MCP 连接拖住。预热可以理解成提前连一下服务器,避免首次调用慢。

数据流:它启动一个假的 websocket 模型服务器和一个只接连接但不响应的 MCP 地址 → 构建 Codex 后等到 MCP 连接开始卡住 → 调用 shutdown_and_wait,确认能很快退出,且不会再发 websocket 请求。

调用关系:它验证启动阶段和关闭阶段的边界行为,防止后台 MCP 启动任务让测试或真实程序关不掉。

调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 9 个(from_millis, from_secs, assert!, format!, skip_if_no_network!, bind, sleep, timeout, vec!)。

stdio_server_uses_configured_cwd_before_runtime_fallback609–672 ↗
async fn stdio_server_uses_configured_cwd_before_runtime_fallback() -> anyhow::Result<()>

作用:测试 stdio MCP 服务器如果配置里明确写了工作目录,就必须从那个目录启动,而不是用 Codex 当前目录。

数据流:它创建一个专门的目录,把该目录写进 MCP 配置 → 启动 Codex 和 MCP 服务器 → 调用 cwd 工具取回实际目录,再和配置目录比较。

调用关系:它依赖 remote_aware_stdio_server_bin 找到测试服务器,调用 call_cwd_tool 执行工具,再用 assert_cwd_tool_output 做断言。

调用图:调用 5 个内部函数(start_mock_server, test_codex, assert_cwd_tool_output, call_cwd_tool, remote_aware_stdio_server_bin);外部调用 6 个(clone, new, new, wait_for_mcp_server, skip_if_no_network!, skip_if_wine_exec!)。

local_stdio_server_uses_runtime_fallback_cwd_when_config_omits_cwd677–736 ↗
async fn local_stdio_server_uses_runtime_fallback_cwd_when_config_omits_cwd() -> anyhow::Result<()>

作用:测试本地 stdio MCP 配置没写工作目录时,会退回到 Codex 的运行目录。这样相对路径命令也能按用户项目目录来找。

数据流:它把测试服务器复制到工作目录下的相对路径 → 配置 MCP 命令但不配置 cwd → 启动后调用 cwd 工具 → 验证返回目录是 Codex 当前工作目录。

调用关系:这是 Unix 本地专用测试;它和 configured cwd 测试配成一组,分别验证“有配置”和“没配置”的目录规则。

调用图:调用 4 个内部函数(start_mock_server, test_codex, assert_cwd_tool_output, call_cwd_tool);外部调用 7 个(clone, new, new, from, cargo_bin, wait_for_mcp_server, skip_if_no_network!)。

stdio_mcp_tool_call_includes_sandbox_state_meta739–834 ↗
async fn stdio_mcp_tool_call_includes_sandbox_state_meta() -> anyhow::Result<()>

作用:测试调用 stdio MCP 工具时,结果里会带上沙箱状态元数据。沙箱就是限制工具能访问哪些文件和能力的安全边界。

数据流:它让模型调用 sandbox_meta 工具 → Codex 执行工具并把结果回传 → 测试拆开输出 JSON,检查里面有沙箱策略、沙箱工作目录和 legacy landlock 标志。

调用关系:它用 turn_permission_fields 算出期望沙箱策略,用 split_wall_time_wrapped_output 拆开回传内容,确认 MCP 和权限系统之间的信息没有丢。

调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, turn_permission_fields, remote_aware_stdio_server_bin, split_wall_time_wrapped_output, read_only);外部调用 9 个(assert!, assert_eq!, wait_for_mcp_server, format!, from_str, to_value, skip_if_no_network!, skip_if_wine_exec!, vec!)。

stdio_mcp_parallel_tool_calls_default_false_runs_serially837–953 ↗
async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::Result<()>

作用:测试 MCP 服务器默认没有声明支持并发时,两个会修改状态的工具调用应该排队执行。这样能避免两个工具同时改同一份东西。

数据流:它让模型一次发出两个 sync 工具调用 → 启动默认不支持并发的 MCP 服务器 → 收集工具开始和结束事件 → 验证一个调用结束后另一个才开始,并检查两个输出都是成功 JSON。

调用关系:它用 auto_approved_user_turn 避免审批影响调度,用 split_wall_time_wrapped_output 检查最终回传;和后面的并发 opt-in 测试形成对照。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, auto_approved_user_turn, remote_aware_stdio_server_bin, split_wall_time_wrapped_output);外部调用 14 个(new, assert!, assert_eq!, Begin, End, wait_for_event, wait_for_mcp_server, format!, json!, from_str (+4 more))。

stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in956–1056 ↗
async fn stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in() -> anyhow::Result<()>

作用:测试只读工具即使服务器没声明支持并发,也可以同时执行。只读表示不会改东西,所以并行更安全。

数据流:它让模型发出两个 sync_readonly 调用,并在工具参数里设置一个屏障,要求两个调用都到达才继续 → 如果 Codex 串行执行就会超时 → 成功时检查两个工具都返回 {result: ok}。

调用关系:它用 read_only_user_turn 提交请求,靠测试服务器的屏障机制证明并发真的发生;输出检查仍用 split_wall_time_wrapped_output。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, remote_aware_stdio_server_bin, split_wall_time_wrapped_output);外部调用 9 个(assert_eq!, wait_for_event, wait_for_mcp_server, format!, json!, from_str, skip_if_no_network!, skip_if_wine_exec!, vec!)。

stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently1059–1148 ↗
async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Result<()>

作用:测试服务器明确声明支持并发时,普通可变工具也能并行执行。这里验证配置开关确实影响调度。

数据流:它配置 supports_parallel_tool_calls=true,模型发出两个 sync 调用,并用屏障要求两者同时到达 → 等整轮完成后检查两个结果都成功。

调用关系:它和默认串行测试使用类似工具,但配置项相反;通过 auto_approved_user_turn 聚焦测试并发开关本身。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, auto_approved_user_turn, remote_aware_stdio_server_bin, split_wall_time_wrapped_output);外部调用 9 个(assert_eq!, wait_for_event, wait_for_mcp_server, format!, json!, from_str, skip_if_no_network!, skip_if_wine_exec!, vec!)。

stdio_image_responses_round_trip1152–1290 ↗
async fn stdio_image_responses_round_trip() -> anyhow::Result<()>

作用:测试 stdio MCP 工具返回图片时,Codex 能把 MCP 图片内容转换成模型接口需要的图片输入格式。

数据流:它把一张 PNG data URL 通过环境变量传给测试 MCP 服务器 → 模型调用 image 工具 → 测试检查 MCP 事件里的图片内容、base64 数据、mime 类型,并检查回传给模型的是 input_image。

调用关系:它是图片返回链路的基础测试;用 assert_wall_time_header 确认图片项前还有运行耗时说明。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_wall_time_header, read_only_user_turn, remote_aware_stdio_server_bin);外部调用 8 个(assert_eq!, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, skip_if_wine_exec!, unreachable!, vec!)。

stdio_image_responses_resize_large_image1294–1396 ↗
async fn stdio_image_responses_resize_large_image() -> anyhow::Result<()>

作用:测试开启图片缩放功能后,MCP 返回的大图片会被缩小到合适尺寸再发给模型。这样能减少请求体和模型处理压力。

数据流:它生成一张 3000x2000 的 PNG,放进工具参数 → 打开 ResizeAllImages 功能 → 调用 image_scenario 工具 → 从回传 data URL 解码图片,确认尺寸变成 1920x1280。

调用关系:它使用 remote_aware_stdio_server_bin 启动同一个测试服务器,但重点验证图片管线里的缩放功能,而不是 MCP 调用本身。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, remote_aware_stdio_server_bin, new);外部调用 14 个(ImageRgba8, from_pixel, new, assert_eq!, wait_for_event, wait_for_mcp_server, format!, Rgba, load_from_memory, json! (+4 more))。

stdio_image_responses_preserve_original_detail_metadata1400–1487 ↗
async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Result<()>

作用:测试 MCP 图片声明 detail 为 original 时,Codex 会保留这个细节标记。detail 可以理解成告诉模型按什么清晰度处理图片。

数据流:它让模型调用返回 original detail 图片的场景 → 等工具和整轮完成 → 检查发给模型的 input_image 中 image_url 没变,detail 字段仍是 original。

调用关系:它用 assert_wall_time_header 检查前置耗时文本;和 resize 测试一起覆盖图片内容和图片元数据两方面。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_wall_time_header, read_only_user_turn, remote_aware_stdio_server_bin);外部调用 7 个(assert_eq!, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, skip_if_wine_exec!, vec!)。

stdio_image_responses_are_sanitized_for_text_only_model1491–1645 ↗
async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Result<()>

作用:测试当当前模型只支持文字输入时,MCP 返回的图片不会硬塞给模型,而是替换成一段说明文字。

数据流:它先让假模型列表返回一个只支持 Text 的模型 → 配置 MCP image 工具 → 用该模型提交请求 → 最后检查回传给模型的是 JSON 文本,内容说明图片已省略。

调用关系:它直接调用 read_only_user_turn_with_model 指定文本模型;用 mount_models_once 准备模型能力,用 split_wall_time_wrapped_output 拆出最终输出。

调用图:调用 9 个内部函数(mount_models_once, mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn_with_model, remote_aware_stdio_server_bin, split_wall_time_wrapped_output, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(assert_eq!, wait_for_event, wait_for_mcp_server, format!, from_str, skip_if_no_network!, skip_if_wine_exec!, vec!)。

stdio_server_propagates_whitelisted_env_vars1649–1767 ↗
async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()>

作用:测试配置了环境变量白名单时,本地环境里的指定变量会传给 stdio MCP 服务器。

数据流:它先用 EnvVarGuard 临时设置 MCP_TEST_VALUE → MCP 配置里只把这个变量列入 env_vars → 调用 echo 工具 → 检查工具返回的 env 字段等于设置值。

调用关系:它用 EnvVarGuard::set 确保测试结束会恢复环境变量;和显式 source 测试一起覆盖环境变量传递规则。

调用图:调用 7 个内部函数(set, mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, remote_aware_stdio_server_bin);外部调用 10 个(new, assert!, assert_eq!, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, skip_if_wine_exec!, unreachable!, vec!)。

stdio_server_propagates_explicit_local_env_var_source1771–1863 ↗
async fn stdio_server_propagates_explicit_local_env_var_source() -> anyhow::Result<()>

作用:测试环境变量声明 source 为 local 时,会从本地测试进程环境读取并传给 MCP 服务器。

数据流:它设置一个本地环境变量 → 在 MCP env_vars 中声明该变量 source=local → 调用 echo 工具并要求读取这个变量 → 检查返回值就是本地设置的值。

调用关系:它证明显式 local 来源能跨到 MCP 进程;使用 EnvVarGuard::set 做临时环境保护,使用 read_only_user_turn 驱动工具调用。

调用图:调用 7 个内部函数(set, mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, remote_aware_stdio_server_bin);外部调用 9 个(new, assert_eq!, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, skip_if_wine_exec!, unreachable!, vec!)。

remote_stdio_env_var_source_does_not_copy_local_env1867–1961 ↗
async fn remote_stdio_env_var_source_does_not_copy_local_env() -> anyhow::Result<()>

作用:测试远程 source 的环境变量不会偷偷从本地主机复制过去。这个很重要,因为远程环境应该只用远程自己的秘密和变量。

数据流:它只在远程测试环境运行 → 本地设置一个同名变量,但 MCP 配置声明 source=remote → 调用 echo 工具 → 期望工具看到的 env 是 Null,而不是本地值。

调用关系:它和 local source 测试正好相反,验证远程隔离;仍然通过 remote_aware_stdio_server_bin 和 build_with_remote_env 走远程路径。

调用图:调用 7 个内部函数(set, mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, remote_aware_stdio_server_bin);外部调用 10 个(new, assert_eq!, test_environment, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, skip_if_wine_exec!, unreachable!, vec!)。

RemoteStreamableHttpServer::drop1989–1998 ↗
fn drop(&mut self)

作用:远程 HTTP MCP 测试服务器对象被丢弃时,尽量杀掉远程进程并删除复制过去的临时文件。它是测试清理的保险丝。

数据流:输入是对象自身保存的容器名、进程号和文件路径 → 它先调用 kill → 如果有临时文件,就通过 docker exec rm -f 删除它们;错误被忽略。

调用关系:当 StreamableHttpTestServerProcess::Remote 生命周期结束时自动触发;它调用 RemoteStreamableHttpServer::kill 做停止动作。

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

RemoteStreamableHttpServer::kill2003–2007 ↗
fn kill(&self)

作用:停止远程容器里的 Streamable HTTP MCP 测试服务器进程。它用在显式 shutdown 和自动 drop 清理里。

数据流:输入来自对象里的容器名和 pid → 它执行 docker exec kill pid → 不返回进程状态,失败也不打断清理流程。

调用关系:RemoteStreamableHttpServer::drop 会调用它;StreamableHttpTestServer::shutdown 在远程分支也会调用它。

调用图:被 1 处调用(drop);外部调用 1 个(new)。

StreamableHttpTestServer::url2012–2014 ↗
fn url(&self) -> &str

作用:取出测试 HTTP MCP 服务器的 /mcp 地址。Codex 配置需要这个 URL 才知道连哪里。

数据流:输入是服务器对象自身 → 返回内部 server_url 字符串引用 → 不修改任何状态。

调用关系:Streamable HTTP 相关测试启动服务器后调用它,把 URL 写进 MCP 配置。

StreamableHttpTestServer::shutdown2017–2038 ↗
async fn shutdown(mut self)

作用:关闭本地或远程的 Streamable HTTP MCP 测试服务器。它让测试结束后不留下后台进程。

数据流:输入是服务器对象自身 → 本地进程先检查是否已退出,没退出就 kill 并等待;远程进程调用 kill → 出错时只打印日志。

调用关系:streamable_http_tool_call_round_trip 和 OAuth 测试实现会在验证完成后调用它;远程清理最终还会受 RemoteStreamableHttpServer::drop 兜底。

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

streamable_http_tool_call_round_trip2045–2185 ↗
async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()>

作用:测试 Streamable HTTP 方式的 MCP 工具调用能完整跑通。HTTP 方式不像 stdio 启子进程通信,而是 Codex 通过 URL 调工具服务器。

数据流:它启动假模型服务器并脚本化一次 echo 工具调用 → 启动 HTTP MCP 测试服务器 → 把 URL 写进 Codex 配置 → 提交用户请求,检查工具开始、工具结果、环境变量和整轮完成。

调用关系:它调用 start_streamable_http_test_server 适配本地或远程服务器,最后调用 shutdown 清理;这是 HTTP MCP 无 OAuth 场景的主测试。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, start_streamable_http_test_server);外部调用 8 个(assert!, assert_eq!, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, unreachable!, vec!)。

streamable_http_with_oauth_round_trip2191–2211 ↗
fn streamable_http_with_oauth_round_trip() -> anyhow::Result<()>

作用:为 OAuth 版 HTTP MCP 测试创建一个单独线程和 Tokio 运行时。这样可以给测试线程设置更大的栈,避免复杂异步流程栈不够。

数据流:它创建指定栈大小的新线程 → 在线程里建 Tokio 多线程运行时 → block_on 调用 streamable_http_with_oauth_round_trip_impl → 把执行结果或 panic 转成 anyhow 结果返回。

调用关系:这是同步测试入口,真正测试内容在 streamable_http_with_oauth_round_trip_impl;它还用 serial 保证 CODEX_HOME 相关文件不被其他测试同时改。

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

streamable_http_with_oauth_round_trip_impl2213–2374 ↗
async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()>

作用:测试带 OAuth 凭证的 Streamable HTTP MCP 调用能成功。OAuth 可以理解成用访问令牌证明“我有权限访问这个服务器”。

数据流:它启动要求 bearer token 的 HTTP MCP 服务器 → 创建临时 CODEX_HOME,并写入备用 OAuth token 文件 → 配置 Codex 使用文件凭证库和 MCP URL → 提交请求并验证 echo 工具成功、环境变量正确、整轮完成。

调用关系:它由 streamable_http_with_oauth_round_trip 包在线程里运行;它调用 start_streamable_http_test_server 启服务器,调用 write_fallback_oauth_tokens 准备凭证。

调用图:调用 8 个内部函数(set, mount_sse_once, sse, start_mock_server, test_codex, read_only_user_turn, start_streamable_http_test_server, write_fallback_oauth_tokens);外部调用 10 个(new, assert!, assert_eq!, wait_for_event, wait_for_mcp_server, format!, skip_if_no_network!, tempdir, unreachable!, vec!)。

start_streamable_http_test_server2377–2423 ↗
async fn start_streamable_http_test_server(
    expected_env_value: &str,
    expected_token: Option<&str>,
) -> anyhow::Result<Option<StreamableHttpTestServer>>

作用:按当前测试环境启动 Streamable HTTP MCP 测试服务器。本地就启动本机子进程,远程就把服务器放进 Docker 容器。

数据流:输入是期望环境变量值和可选 bearer token → 它先找测试服务器二进制 → 如果有远程容器就调用 start_remote_streamable_http_test_server;否则挑一个本地端口、启动子进程、等待 metadata 可访问 → 返回服务器对象,找不到二进制则返回 None 表示跳过。

调用关系:HTTP round trip 和 OAuth 测试都通过它启动服务器;它把本地等待交给 wait_for_local_streamable_http_server,把远程启动交给 start_remote_streamable_http_test_server。

调用图:调用 2 个内部函数(start_remote_streamable_http_test_server, wait_for_local_streamable_http_server);被 2 处调用(streamable_http_tool_call_round_trip, streamable_http_with_oauth_round_trip_impl);外部调用 8 个(from_secs, bind, new, cargo_bin, Local, test_environment, eprintln!, format!)。

start_remote_streamable_http_test_server2426–2503 ↗
async fn start_remote_streamable_http_test_server(
    container_name: &str,
    rmcp_http_server_bin: &Path,
    expected_env_value: &str,
    expected_token: Option<&str>,
) -> anyhow::Result<Stream

作用:在远程 Docker 容器里启动 Streamable HTTP MCP 测试服务器,并算出 Codex 可以访问的 URL。

数据流:输入是容器名、服务器二进制路径、环境变量值和可选 token → 它复制二进制,拼 shell 环境变量,nohup 启动进程,读取绑定地址文件,查容器 IP,探测 metadata 可用 → 输出带远程进程信息的 StreamableHttpTestServer。

调用关系:start_streamable_http_test_server 在远程环境调用它;它依赖 copy_binary_to_remote_env、wait_for_remote_bound_addr、remote_container_ip 和远程 HTTP 探测函数。

调用图:调用 5 个内部函数(copy_binary_to_remote_env, remote_container_ip, wait_for_remote_bound_addr, wait_for_remote_streamable_http_server, wait_for_streamable_http_metadata);被 1 处调用(start_streamable_http_test_server);外部调用 7 个(from_secs, new, from_utf8, Remote, ensure!, format!, vec!)。

sh_single_quote2506–2508 ↗
fn sh_single_quote(value: &str) -> String

作用:把字符串安全地包成 shell 单引号形式。这样拼 Docker 里的小脚本时,值里有引号也不容易把命令弄坏。

数据流:输入是普通字符串 → 它把内部单引号替换成 shell 可接受的转义写法 → 输出带外层单引号的字符串。

调用关系:远程 HTTP 服务器启动函数用它拼环境变量和路径,避免测试值或路径中的特殊字符影响 shell 命令。

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

wait_for_remote_bound_addr2511–2538 ↗
async fn wait_for_remote_bound_addr(
    container_name: &str,
    bound_addr_file: &str,
    timeout: Duration,
) -> anyhow::Result<SocketAddr>

作用:等待远程 HTTP 测试服务器把自己绑定的地址写进文件。因为端口可能是系统随机分配的,必须等服务器告诉我们。

数据流:输入是容器名、地址文件路径和超时时间 → 它循环 docker exec cat 文件 → 成功时解析成 SocketAddr 返回;超时就返回错误。

调用关系:start_remote_streamable_http_test_server 启动远程进程后调用它,拿到实际端口后才能组装 server_url。

调用图:被 1 处调用(start_remote_streamable_http_test_server);外部调用 6 个(from_millis, now, new, from_utf8, anyhow!, sleep)。

remote_container_ip2541–2570 ↗
fn remote_container_ip(container_name: &str) -> anyhow::Result<String>

作用:读取远程 Docker 容器的 IP 地址。测试进程需要这个地址才能从主机侧或远程执行侧探测 HTTP 服务。

数据流:输入是容器名 → 它执行 docker inspect → 从输出里取第一条非空 IP;如果没取到,就退回 127.0.0.1。

调用关系:start_remote_streamable_http_test_server 在拿到端口后调用它,用 IP 加端口拼出 MCP 服务器 URL。

调用图:被 1 处调用(start_remote_streamable_http_test_server);外部调用 3 个(new, from_utf8, ensure!)。

wait_for_local_streamable_http_server2573–2622 ↗
async fn wait_for_local_streamable_http_server(
    server_child: &mut Child,
    server_url: &str,
    timeout: Duration,
) -> anyhow::Result<()>

作用:等待本地 HTTP MCP 测试服务器真的启动好,并能提供 OAuth metadata。metadata 可以理解成服务器告诉客户端“我怎么鉴权”的说明页。

数据流:输入是子进程句柄、服务器 URL 和超时时间 → 它循环检查进程是否提前退出,并 GET metadata URL → 收到 HTTP 200 就返回,否则超时返回详细错误。

调用关系:start_streamable_http_test_server 在本地分支启动子进程后调用它;它用 streamable_http_metadata_url 生成要探测的地址。

调用图:调用 1 个内部函数(streamable_http_metadata_url);被 1 处调用(start_streamable_http_test_server);外部调用 7 个(try_wait, from_millis, now, anyhow!, builder, sleep, timeout)。

wait_for_remote_streamable_http_server2625–2674 ↗
async fn wait_for_remote_streamable_http_server(
    server_url: &str,
    timeout: Duration,
) -> anyhow::Result<()>

作用:通过远程执行环境的 HTTP 客户端,等待远程 HTTP MCP 服务器可访问。它验证的不只是主机能访问,而是远程侧 MCP 客户端也能访问。

数据流:输入是服务器 URL 和超时时间 → 它读取远程执行 websocket URL,创建测试环境 HTTP 客户端 → 循环发 GET metadata 请求 → HTTP 200 时返回,超时或错误时返回说明。

调用关系:start_remote_streamable_http_test_server 在拼出远程 URL 后调用它,确保交给 Codex 前服务已经从远程侧可达。

调用图:调用 2 个内部函数(streamable_http_metadata_url, create_for_tests);被 1 处调用(start_remote_streamable_http_test_server);外部调用 6 个(from_millis, now, new, anyhow!, var, sleep)。

wait_for_streamable_http_metadata2677–2718 ↗
async fn wait_for_streamable_http_metadata(
    server_url: &str,
    timeout: Duration,
) -> anyhow::Result<()>

作用:从当前测试进程等待 HTTP MCP 服务器的 OAuth metadata 可访问。OAuth 场景需要它确认鉴权说明端点已经就绪。

数据流:输入是服务器 URL 和超时时间 → 它循环用普通 reqwest 客户端 GET metadata URL → 收到 HTTP 200 返回,否则超时返回错误。

调用关系:start_remote_streamable_http_test_server 在 expected_token 存在时调用它,额外确认主机侧也能访问 OAuth metadata。

调用图:调用 1 个内部函数(streamable_http_metadata_url);被 1 处调用(start_remote_streamable_http_test_server);外部调用 6 个(from_millis, now, anyhow!, builder, sleep, timeout)。

streamable_http_metadata_url2721–2724 ↗
fn streamable_http_metadata_url(server_url: &str) -> String

作用:根据 MCP 的 /mcp 地址拼出 OAuth metadata 地址。这样等待函数不用各自手写路径规则。

数据流:输入是 server_url → 如果末尾是 /mcp 就先去掉 → 再拼上固定的 /.well-known/oauth-authorization-server/mcp 路径 → 输出完整 URL。

调用关系:本地等待、远程等待和 metadata 专用等待函数都调用它,统一 Streamable HTTP 测试服务器的探测地址。

调用图:被 3 处调用(wait_for_local_streamable_http_server, wait_for_remote_streamable_http_server, wait_for_streamable_http_metadata);外部调用 1 个(format!)。

write_fallback_oauth_tokens2726–2755 ↗
fn write_fallback_oauth_tokens(
    home: &Path,
    server_name: &str,
    server_url: &str,
    client_id: &str,
    access_token: &str,
    refresh_token: &str,
) -> anyhow::Result<()>

作用:给测试用 CODEX_HOME 写入一份备用 OAuth 凭证文件。这样 Codex 启动后能找到访问 MCP HTTP 服务器需要的 token。

数据流:输入是 home 路径、服务器名、服务器 URL、client_id、access_token 和 refresh_token → 它计算一小时后的过期时间,组装 JSON → 写到 home/.credentials.json。

调用关系:OAuth round trip 实现会在构建 Codex fixture 前调用它;后续 Codex 的 OAuth 凭证库从这个文件里读取 token。

调用图:被 1 处调用(streamable_http_with_oauth_round_trip_impl);外部调用 6 个(from_secs, join, now, write, json!, to_vec)。

EnvVarGuard::set2763–2769 ↗
fn set(key: &'static str, value: &std::ffi::OsStr) -> Self

作用:临时设置一个环境变量,并记住原来的值。测试用它避免改了全局环境后影响别的测试。

数据流:输入是环境变量名和新值 → 它读取原值,设置新值 → 返回 EnvVarGuard,里面保存变量名和旧值。

调用关系:环境变量传递测试和 OAuth 测试会调用它;对象离开作用域时 EnvVarGuard::drop 会自动恢复。

调用图:外部调用 2 个(set_var, var_os)。

EnvVarGuard::drop2773–2780 ↗
fn drop(&mut self)

作用:EnvVarGuard 被销毁时恢复环境变量。原来有值就设回去,原来没有就删除。

数据流:输入是 guard 保存的变量名和原始值 → 如果 original 是 Some,就 set_var 恢复;如果是 None,就 remove_var 删除 → 修改进程环境,不返回数据。

调用关系:它由 Rust 的 Drop 机制自动调用,给所有使用 EnvVarGuard::set 的测试做收尾,防止全局环境污染。

调用图:外部调用 2 个(remove_var, set_var)。

rmcp-client/tests/resources.rs源码 ↗
testtest run

这个测试文件解决的是“客户端会不会真的和 MCP 资源服务器正常说话”的问题。MCP 可以理解成一种让工具暴露资源和能力的协议;这里测试的资源就像服务器提供的一张便签。测试先找到测试用服务器程序,再准备客户端初始化信息,声明自己支持“询问用户补充信息”的能力。然后它启动本地 stdio 服务器,stdio 指标准输入输出,像两个人隔着一根管子传纸条。连接成功后,测试让客户端初始化、列出资源、列出资源模板,再读取指定的 memo 资源。每一步都用明确的期望值检查返回结果。如果服务器少报了资源、字段写错了、模板格式变了,或者读取内容不对,这个测试都会失败,所以它能及时发现客户端和服务器协议对接上的问题。

函数细节3
stdio_server_bin26–28 ↗
fn stdio_server_bin() -> Result<PathBuf, CargoBinError>

作用:这个函数用来找到测试用的本地服务器可执行文件。测试需要先知道服务器程序在哪里,才能启动它来做真实通信。

数据流:进去没有参数 → 它调用 cargo_bin 查找名为 test_stdio_server 的测试二进制文件 → 出来是这个程序的路径;如果找不到,就返回查找错误。

调用关系:它是整场测试的准备步骤之一。rmcp_client_can_list_and_read_resources 在创建客户端之前调用它,拿到服务器路径后交给 new_stdio_client 去启动本地服务器。

调用图:被 1 处调用(rmcp_client_can_list_and_read_resources);外部调用 1 个(cargo_bin)。

init_params30–43 ↗
fn init_params() -> InitializeRequestParams

作用:这个函数生成客户端初始化时要发给服务器的一包自我介绍信息。它告诉服务器:我是一个测试客户端,我支持哪些能力,并且要使用哪个协议版本。

数据流:进去没有参数 → 它先做一份默认客户端能力表,再加上 elicitation 能力,也就是服务器需要时可以向客户端询问信息;接着填入客户端名称、版本、标题和协议版本 → 出来是一份 InitializeRequestParams 初始化参数。

调用关系:它在客户端连上服务器后被 rmcp_client_can_list_and_read_resources 调用。生成的参数会交给 client.initialize,让客户端和服务器先完成握手,再继续列资源和读资源。

调用图:被 1 处调用(rmcp_client_can_list_and_read_resources);外部调用 3 个(default, new, new)。

rmcp_client_can_list_and_read_resources46–138 ↗
async fn rmcp_client_can_list_and_read_resources() -> anyhow::Result<()>

作用:这是核心测试:验证 rmcp 客户端能通过本地 stdio 服务器完成初始化、列出资源、列出资源模板,并读取一个具体资源的文本内容。有人改客户端、服务器或协议字段时,这个测试能告诉大家有没有把资源功能弄坏。

数据流:进去没有普通参数,测试框架会自动运行它 → 它先找到服务器程序并创建 stdio 客户端,然后用 init_params 初始化连接;初始化时还提供一个回调,服务器如果发起询问,客户端就自动接受并返回空内容;之后它请求资源列表,检查里面有没有指定的 memo 资源且字段完全正确;再请求资源模板列表,检查模板内容;最后读取 memo://codex/example-note,确认返回的是预期文本 → 出来是测试成功的 Ok,或者在任何一步出错、超时、断言不符时让测试失败。

调用关系:它是这个文件的总导演。它先调用 stdio_server_bin 准备服务器路径,再调用 init_params 准备握手信息,然后把实际通信交给 RmcpClient 的 new_stdio_client、initialize、list_resources、list_resource_templates 和 read_resource 等方法完成。

调用图:调用 4 个内部函数(new_stdio_client, new, init_params, stdio_server_bin);外部调用 7 个(new, new, from_secs, new, new, assert_eq!, current_dir)。

工具定义与转换行为

本组跟踪工具建模,从核心定义和发现,到 MCP 与动态工具转换、代码模式适配、Responses API 形态,以及 schema-policy 测试夹具。

tools/src/tool_definition_tests.rs源码 ↗
testtest run

在这套系统里,一个 ToolDefinition 可以理解成“某个工具的登记卡”:上面写着工具叫什么、干什么、输入长什么样、输出长什么样,以及要不要先别完整加载。这个测试文件先造出一张固定的示例登记卡,然后检查两个动作是否准确:renamed 只能改名字,不能动别的内容;into_deferred 要把输出格式说明删掉,并把“延迟加载”开关打开。这里的测试像给机器零件做校准:不是测复杂流程,而是确认两个很基础的按钮按下去后,只产生该有的变化,别顺手改坏其他字段。

函数细节3
tool_definition6–20 ↗
fn tool_definition() -> ToolDefinition

作用:这个函数造出一个标准的 ToolDefinition 测试样本,供后面的测试反复使用。这样每个测试都从同一份“基准登记卡”开始,比较结果时更清楚。

数据流:进去没有外部参数 → 它创建一个名字为 lookup_order、描述为 Look up an order 的工具定义,并给它一个空的输入 JSON Schema(JSON Schema 是用来描述 JSON 数据长什么样的规则),再放入一个简单的输出 schema → 出来的是一份完整的 ToolDefinition,defer_loading 初始为 false。

调用关系:它是两个测试的共同准备步骤。renamed_overrides_name_only 和 into_deferred_drops_output_schema_and_sets_defer_loading 都用它来生成原始样本,再把样本交给被测试的方法。它内部会用 JsonSchema::object 生成输入规则,用 BTreeMap::new 准备空字段表,并用 json! 宏生成输出规则。

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

renamed_overrides_name_only23–31 ↗
fn renamed_overrides_name_only()

作用:这个测试确认 renamed 这个改名功能只会改工具名字,不会偷偷改描述、输入规则、输出规则或延迟加载设置。它防的是“改个名字却把别的登记信息也弄变了”这种错误。

数据流:进去的是测试运行器自动启动的测试环境 → 它先拿 tool_definition 做出一份原始工具定义,再调用 renamed,把名字换成 mcp__orders__lookup_order,然后和一份“只有 name 被换掉、其他字段照旧”的期望结果比较 → 如果完全一样,测试通过;如果任何字段不一样,assert_eq! 会让测试失败。

调用关系:它由 Rust 的测试框架在跑测试时调用。它自己不处理底层比较细节,而是把实际结果和期望结果交给 assert_eq! 断言宏;样本数据来自 tool_definition。这个测试专门守住 ToolDefinition::renamed 的行为边界。

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

into_deferred_drops_output_schema_and_sets_defer_loading34–43 ↗
fn into_deferred_drops_output_schema_and_sets_defer_loading()

作用:这个测试确认 into_deferred 会把工具定义改成“延迟加载”版本:先不带输出格式说明,并把延迟加载标记打开。它确保系统在需要轻量登记工具时,不会还带着完整输出说明。

数据流:进去的是测试运行器自动启动的测试环境 → 它用 tool_definition 建出一份完整工具定义,再调用 into_deferred,把它转换成延迟加载形式;随后拿结果和期望值比较,期望值是 output_schema 变成 None、defer_loading 变成 true,其他字段不变 → 两边一致就通过,不一致就由 assert_eq! 报错。

调用关系:它同样由 Rust 测试框架执行。它依赖 tool_definition 提供固定起点,然后把转换结果交给 assert_eq! 检查。它主要保护 ToolDefinition::into_deferred 这个转换动作,避免以后改代码时丢错字段或忘记打开延迟加载开关。

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

tools/src/tool_spec_tests.rs源码 ↗
testtest/CI

这份文件像一套“出厂质检表”,专门检查各种工具定义能不能按约定包装好。这里的“工具”可以理解为模型能调用的能力,比如普通函数工具、工具命名空间、网页搜索、图片生成、自由格式命令等。测试会先构造一些典型工具,再把它们变成内部名字或 JSON,最后用断言(一种检查“实际结果是否等于期望结果”的测试语句)逐项对比。它特别关注发到外部接口的“线格式”,也就是网络上传输时真正长什么样。这样做很重要,因为外部 API 通常很挑剔:字段叫错、少了顶层 name、搜索位置格式不对,都可能让请求失败或行为异常。

函数细节6
tool_spec_name_covers_all_variants21–91 ↗
fn tool_spec_name_covers_all_variants()

作用:这个测试确认每一种 ToolSpec 工具类型都能给出正确的名字。有人新增或改动工具类型时,它能防止名字规则被不小心弄坏。

数据流:进去的是几种手工创建的工具样本,包括函数工具、命名空间、工具搜索、图片生成、网页搜索和自由格式工具。测试分别调用它们的 name 方法,拿到实际名字,再用 assert_eq! 对比期望名字。出来的结果不是业务数据,而是测试通过或失败;如果某个名字不对,测试就失败并指出差异。

调用关系:它是测试阶段直接运行的检查项,核心动作是把不同 ToolSpec 变体交给各自的 name 逻辑。最后把判断交给 assert_eq!,让测试框架决定是否报错。

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

web_search_config_converts_to_responses_api_types94–119 ↗
fn web_search_config_converts_to_responses_api_types()

作用:这个测试确认配置文件里的网页搜索设置,能正确变成 Responses API 需要的内部类型。它防止“配置里写的是一回事,真正发出去又变成另一回事”。

数据流:进去的是配置层的网页搜索过滤条件和用户位置,比如允许访问的域名、国家、城市、时区等。测试用 from 转换成 Responses API 使用的类型,然后和手写的期望结果做比较。出来的是测试是否通过;通过就说明转换没有丢字段、没有改错值。

调用关系:它位于配置到接口格式之间的测试关口。测试本身只负责造输入和检查输出,具体相等性判断交给 assert_eq! 完成。

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

create_tools_json_for_responses_api_includes_top_level_name122–150 ↗
fn create_tools_json_for_responses_api_includes_top_level_name()

作用:这个测试确认普通函数工具转成 Responses API JSON 时,顶层会带上 name 字段。这个字段很关键,因为外部接口要靠它识别要调用哪个工具。

数据流:进去的是一个名叫 demo 的函数工具,带描述、严格模式标记和一个 foo 字符串参数。测试调用 create_tools_json_for_responses_api,把工具列表变成 JSON 数组。出来的是一段期望格式的 JSON;测试会检查里面是否有 type、name、description、strict 和 parameters 等字段,并且值完全正确。

调用关系:它测试的是工具定义进入“发给 Responses API 之前的 JSON 生成步骤”。生成工作交给 create_tools_json_for_responses_api,检查工作交给 assert_eq!。

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

namespace_tool_spec_serializes_expected_wire_shape153–195 ↗
fn namespace_tool_spec_serializes_expected_wire_shape()

作用:这个测试确认“命名空间工具”序列化成 JSON 后,外层命名空间和里面的子工具都长成接口要求的样子。命名空间可以理解为给一组工具套一个文件夹。

数据流:进去的是一个名为 mcp__demo__ 的命名空间,里面放了一个 lookup_order 函数工具,并带有 order_id 参数。测试用 serde_json::to_value 把它转成 JSON 值,再和期望 JSON 对比。出来的是测试通过或失败;失败通常说明字段层级、type 名称、参数结构等和接口约定不一致。

调用关系:它检查 ToolSpec::Namespace 这条序列化路径。序列化由 serde_json 触发,最后由 assert_eq! 对比真实 JSON 和样板 JSON。

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

web_search_tool_spec_serializes_expected_wire_shape198–233 ↗
fn web_search_tool_spec_serializes_expected_wire_shape()

作用:这个测试确认网页搜索工具转成 JSON 时,会正确带上外部联网开关、域名过滤、用户大致位置、搜索上下文大小和内容类型。它保护的是网页搜索请求的完整格式。

数据流:进去的是一个 WebSearch 工具样本,包含允许外部联网、只搜 example.com、用户大致在美国旧金山、上下文大小为 high、内容类型为 text 和 image。测试把它序列化成 JSON,再与期望 JSON 对比。出来的是测试结果;如果枚举值比如 approximate 或 high 写错,或者字段嵌套错了,测试会失败。

调用关系:它专门覆盖网页搜索工具的 JSON 线格式。serde_json 负责把结构转成 JSON,assert_eq! 负责把结果和外部 API 期望的形状逐项比对。

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

tool_search_tool_spec_serializes_expected_wire_shape236–268 ↗
fn tool_search_tool_spec_serializes_expected_wire_shape()

作用:这个测试确认工具搜索工具转成 JSON 时,执行方式、描述和参数规则都符合约定。工具搜索可以理解为让系统按查询词去找可用工具。

数据流:进去的是一个 ToolSearch 工具样本,执行方式是 sync,参数里有必填的 query 字符串,并禁止额外参数。测试把它序列化成 JSON,然后和期望 JSON 对比。出来的是测试是否通过;它会检查 required 和 additionalProperties 这类参数约束有没有被正确写进 JSON。

调用关系:它检查 ToolSpec::ToolSearch 的序列化路径,确保搜索工具发到接口前的说明书没有走样。具体转换由 serde_json 完成,最终判断交给 assert_eq!。

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

tools/src/tool_discovery_tests.rs源码 ↗
testtest run

这个文件不是真正给用户运行功能的代码,而是“防出错检查”。工具发现可以理解成应用里的一张工具清单,里面可能有连接器,比如 Google Calendar,也可能有插件,比如 Slack。第一个测试确认这些工具类型在变成 JSON(一种常见的数据交换格式)时,用的是外部接口约定好的小写名字,比如 connector 和 install。这样前端或服务器两边才听得懂。第二个测试模拟一份工具清单,然后用 codex-tui 这个客户端名去过滤,确认插件会被拿掉,只留下连接器。它的重要性在于:如果以后有人改代码时不小心改了名字或过滤规则,这些测试会立刻失败,提醒开发者别破坏对外兼容性。

函数细节2
discoverable_tool_enums_use_expected_wire_names7–18 ↗
fn discoverable_tool_enums_use_expected_wire_names()

作用:这个测试确认工具类型和操作类型在发到外部时,会被写成接口约定的字符串。这样别的程序收到 JSON 后,才能按预期识别“这是连接器”“这是安装动作”。

数据流:进去的是两个枚举值:DiscoverableToolType::Connector 和 DiscoverableToolAction::Install。测试把它们放进 JSON,再和手写的目标 JSON 作比较。出来的结果不是业务数据,而是测试是否通过:如果实际 JSON 是 connector 和 install,就通过;如果名字变了,就失败。

调用关系:它是在测试阶段由测试框架自动运行的。函数内部主要把实际结果和期望结果交给 assert_eq! 比较,用来守住对外协议里的字段名字不被无意改坏。

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

filter_request_plugin_install_discoverable_tools_for_codex_tui_omits_plugins21–70 ↗
fn filter_request_plugin_install_discoverable_tools_for_codex_tui_omits_plugins()

作用:这个测试确认当客户端是 codex-tui 时,工具列表里的插件会被过滤掉,只保留连接器。它是在防止界面拿到自己不该展示或不支持的工具。

数据流:进去的是一份临时造出来的工具列表,里面有一个 Google Calendar 连接器和一个 Slack 插件,以及客户端名字 codex-tui。测试调用过滤函数后,检查出来的列表只剩 Google Calendar 连接器。它不修改外部状态,只用比较结果来判断过滤规则是否正确。

调用关系:它同样由测试框架自动运行。测试先用 vec! 组装示例数据,再把数据交给 filter_request_plugin_install_discoverable_tools_for_client,最后用 assert_eq! 核对结果,确保该客户端的特殊过滤规则一直有效。

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

tools/src/tool_search_tests.rs源码 ↗
testtest run

这个测试像是在检查一本工具目录的索引有没有编好。代码先手工搭了一个工具说明:它属于 codex_app 这个命名空间,有自己的说明;里面有一个 automation_update 工具,也有说明;工具参数里还有 modeschedule,每个字段也带解释。然后它把这份工具说明交给 ToolSearchInfo::from_tool_spec,让系统生成一段用于搜索匹配的文字。最后用断言把实际生成结果和预期字符串逐字比较。这个文件重要的点是:搜索文本会影响工具能不能被正确找到。如果这里拼错、漏掉说明、或者把同一段元数据重复加入,后面搜索工具时就可能不准,甚至把不该靠前的工具排到前面。

函数细节1
default_search_text_uses_model_visible_namespace_metadata_once6–48 ↗
fn default_search_text_uses_model_visible_namespace_metadata_once()

作用:这个测试确认默认搜索文本会包含模型能看到的命名空间信息、工具信息和参数说明,并且这些信息只出现一次。有人改工具搜索逻辑时,可以靠它发现“漏收文字”或“重复收文字”的问题。

数据流:进去的是测试里临时拼出来的一份工具规格:命名空间叫 codex_app,工具叫 automation_update,参数里有 modescheduletimezone 等说明文字。测试把这些信息交给 ToolSearchInfo::from_tool_spec,让它生成搜索条目。出来的是 search_info.entry.search_text 这段搜索用文本;测试再用 assert_eq! 把它和写死的正确答案比较,如果有一个字不一样,测试就失败。

调用关系:这个函数是测试框架运行到该文件时自动执行的测试用例。它先调用 JsonSchema::objectJsonSchema::string 搭出参数结构,再用 ToolSpec::Namespacevec! 包成一个命名空间工具规格,核心动作交给 ToolSearchInfo::from_tool_spec 完成,最后用 assert_eq! 检查结果是否完全符合预期。

调用图:调用 3 个内部函数(object, string, from_tool_spec);外部调用 4 个(from, assert_eq!, Namespace, vec!)。

tools/src/dynamic_tool_tests.rs源码 ↗
testtest run

这个文件不是正式运行时用的代码,而是测试代码。它像质检员一样,拿两个小样本去检查 parse_dynamic_tool 这个转换函数:外部传来的工具说明里有名字、描述、输入参数格式,以及是否延迟加载。第一个测试故意给了一个不完整的输入格式,只写了参数 id 的 description,没有写清类型;测试期望系统会把它“整理干净”,变成内部统一使用的 JsonSchema 默认字段。第二个测试检查 defer_loading 这个布尔开关,也就是“这个工具要不要先别急着加载”,在转换后仍然保持 true。这里用 serde_json::json! 快速造 JSON 数据,用 assert_eq! 把实际结果和期望结果逐项对比。它的重要性在于:动态工具通常来自外部配置或协议,如果转换时不稳,后面真正调用工具时就可能因为格式不一致而出错。

函数细节2
parse_dynamic_tool_sanitizes_input_schema9–37 ↗
fn parse_dynamic_tool_sanitizes_input_schema()

作用:这个测试确认:当外部工具的输入 schema(可以理解成“参数说明书”)写得不完整时,系统会把它清理成内部能稳定使用的格式。这样后面的代码不用面对乱七八糟的参数描述。

数据流:进去的是一个手工造出来的 DynamicToolFunctionSpec,其中 input_schema 只写了 properties.id.description,没有明确写 type。测试把它交给 parse_dynamic_tool 转换,然后用 assert_eq! 检查出来的 ToolDefinition:名字和描述不变,输入 schema 被整理成一个对象,里面的 id 使用默认 JsonSchema,输出 schema 为空,defer_loading 仍是 false。这个测试本身不产出业务结果,只是在测试通过或失败之间给出判断。

调用关系:测试框架运行到这个用例时,它先用 json! 宏拼出一段 JSON,再调用被测的动态工具解析流程,最后用 assert_eq! 做严格比对。它关注的是“输入格式清洗”这一段是否可靠,失败时会提醒开发者 parse_dynamic_tool 的行为变了。

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

parse_dynamic_tool_preserves_defer_loading40–65 ↗
fn parse_dynamic_tool_preserves_defer_loading()

作用:这个测试确认:动态工具里的 defer_loading 开关在解析后不会被误改。这个开关表示工具是否要延迟加载,丢了或变错都会影响工具什么时候可用。

数据流:进去的是一个 DynamicToolFunctionSpec,名字和描述固定,input_schema 是一个空的 object,defer_loading 被设为 true。测试把它转换成 ToolDefinition 后,用 assert_eq! 检查结果:输入 schema 变成内部表示的空对象,output_schema 仍为空,并且 defer_loading 仍然是 true。也就是说,转换前后的关键开关值保持一致。

调用关系:测试框架执行这个用例时,同样先用 json! 宏创建 JSON 输入,再检查转换结果是否和预期完全一样。它补上了第一个测试没覆盖的重点:不是只看 schema 是否能整理,还要确认加载策略这个开关能原样传过去。

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

tools/src/mcp_tool_tests.rs源码 ↗
testtest run

这里测试的是“把 MCP 工具变成本项目自己的 ToolDefinition”这件事。MCP 可以理解成一种让模型调用外部工具的协议,工具会带名字、说明、输入格式和输出格式。这个文件用几个小例子检查解析规则:如果输入格式只说自己是 object,却没写 properties,代码应该补成一个空的属性表,避免后面的人拿到一个缺字段的结构;如果工具自己声明了输出格式,解析时要原样保留外层结构,不能擅自推断或添加类型;哪怕输出只是 enum,也就是“只能是这些固定值之一”,也不能被改写。文件里的 mcp_tool 是造测试工具的小帮手,三个测试分别覆盖这些容易出错的边角情况。它的重要性在于防止以后改解析代码时,把工具 schema 这种契约弄变形。

函数细节4
mcp_tool8–14 ↗
fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> rmcp::model::Tool

作用:这是测试里用来快速造一个假 MCP 工具的小工具函数。测试不想每次都手写一大段构造代码,所以用它把名字、说明和输入格式包装成 rmcp::model::Tool。

数据流:进去的是工具名字、工具说明和一份 JSON 输入格式说明;它把名字和说明转成字符串,把 JSON 包成 MCP 需要的对象,再调用外部库的构造函数;出来的是一个可以交给解析函数测试的 Tool 对象。

调用关系:它本身不做断言,只负责准备测试材料。三个测试 parse_mcp_tool_inserts_empty_properties、parse_mcp_tool_preserves_top_level_output_schema 和 parse_mcp_tool_preserves_output_schema_without_inferred_type 都先用它造出工具,再把工具交给真正要验证的 parse_mcp_tool。

调用图:被 3 处调用(parse_mcp_tool_inserts_empty_properties, parse_mcp_tool_preserves_output_schema_without_inferred_type, parse_mcp_tool_preserves_top_level_output_schema);外部调用 3 个(new, object, new)。

parse_mcp_tool_inserts_empty_properties17–40 ↗
fn parse_mcp_tool_inserts_empty_properties()

作用:这个测试确认:当 MCP 工具的输入格式只写了 type: object,但没有写 properties 时,解析结果会补上一个空的 properties。这样后续代码看到的是一份完整、稳定的内部格式。

数据流:开始时它用 mcp_tool 造了一个名叫 no_props 的工具,输入 schema 只有 object 类型;然后调用 parse_mcp_tool 解析;最后用 assert_eq! 比较结果,要求内部 ToolDefinition 的 input_schema 是空对象 schema,输出 schema 是默认包装后的空对象,defer_loading 是 false。

调用关系:这个测试站在解析流程的入口处检查一个常见缺省情况。它把准备工具的活交给 mcp_tool,把实际解析交给 parse_mcp_tool,再用断言确认解析结果和预期完全一样。

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

parse_mcp_tool_preserves_top_level_output_schema43–87 ↗
fn parse_mcp_tool_preserves_top_level_output_schema()

作用:这个测试确认:如果 MCP 工具明确提供了顶层输出格式,解析时必须保留它。重点是防止解析代码自作主张,把输出 schema 的外层结构或 required 字段弄丢。

数据流:它先造一个普通工具,再手动给 tool.output_schema 填上一份带 properties 和 required 的 JSON schema;接着调用 parse_mcp_tool;最后比较得到的 ToolDefinition,要求输出 schema 经过 mcp_call_tool_result_output_schema 包装后,里面原来的 result、nested 和 required 信息都还在。

调用关系:这个测试覆盖的是“工具自己声明输出格式”的路径。它用 mcp_tool 准备基础对象,又直接修改 output_schema 来模拟真实 MCP 工具的额外声明,然后检查 parse_mcp_tool 是否把这份声明正确交给 mcp_call_tool_result_output_schema 包装。

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

parse_mcp_tool_preserves_output_schema_without_inferred_type90–120 ↗
fn parse_mcp_tool_preserves_output_schema_without_inferred_type()

作用:这个测试确认:输出格式如果只有 enum,也就是只允许几个固定结果,解析器也应该原样保留,不能因为没看到 type 就替它猜一个类型。

数据流:它先造一个输入为空对象的工具,再给输出 schema 设置成 enum: ["ok", "error"];然后调用 parse_mcp_tool;最后断言内部 ToolDefinition 的输出 schema 仍然包含这两个枚举值,并且没有被额外推断出别的类型。

调用关系:这个测试专门盯住一个容易被“智能补全”误伤的边界情况。它和前一个输出 schema 测试类似,都是先由 mcp_tool 造工具,再改 output_schema,最后通过 parse_mcp_tool 的结果验证输出格式没有被乱改。

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

tools/src/code_mode_tests.rs源码 ↗
testtest run

这份文件不做正式功能,而是像验收清单一样检查别的代码有没有按约定工作。这里的“代码模式”可以理解成:把工具包装成一段像 TypeScript 声明的说明,让模型知道可以调用什么工具、参数长什么样、返回什么。测试会构造几种工具:普通函数工具、自由文本工具、代码执行工具本身、以及不支持的搜索工具。然后调用转换函数,看结果是不是和预期一模一样。重点有三件事:普通工具的描述后面要追加可调用声明;代码执行工具自己的描述不能被重复追加;不支持的工具类型要直接跳过,返回空。这样可以避免模型拿到错误工具说明,导致调用工具时传错参数或误以为某个工具可用。

函数细节4
augment_tool_spec_for_code_mode_augments_function_tools15–66 ↗
fn augment_tool_spec_for_code_mode_augments_function_tools()

作用:这个测试确认普通“函数工具”的说明会被补上一段代码模式可读的调用声明。这样模型不只看到一句人话描述,还能看到参数和返回值该怎么写。

数据流:进去的是一个名叫 lookup_order 的函数工具,带有 order_id 这个字符串参数,以及返回 { ok: boolean } 的输出结构。测试把它交给 augment_tool_spec_for_code_mode,然后拿实际结果和手写的预期结果比较。出来的期望效果是:工具名字、参数、输出格式等都不变,只有 description 后面多了一段 TypeScript 风格的 exec tool declaration。

调用关系:这个函数由 Rust 的测试运行器在跑测试时执行。它的核心动作是检查增强后的工具规格是否完全等于预期,比较工作交给 assert_eq! 这个测试断言宏来完成;如果描述追加少了、追加错了,测试就会失败。

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

augment_tool_spec_for_code_mode_preserves_exec_tool_description69–90 ↗
fn augment_tool_spec_for_code_mode_preserves_exec_tool_description()

作用:这个测试确认代码执行工具本身的说明不会被额外改写。因为它已经是执行代码用的特殊工具,如果再往里塞一段自己的调用声明,反而会让说明变乱。

数据流:进去的是一个自由格式工具,名字是代码模式公开使用的特殊工具名,描述是 Run code,并带着一段 grammar(语法规则,告诉输入文本要符合什么格式)。测试调用 augment_tool_spec_for_code_mode 后,要求出来的工具和进去时完全一样,没有任何字段被改动。

调用关系:这个函数在测试阶段由测试运行器调用。它用 assert_eq! 做精确对比,守住一个边界条件:普通工具可以被增强,但代码执行工具自己不能被重复包装。

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

tool_spec_to_code_mode_tool_definition_returns_augmented_nested_tools93–121 ↗
fn tool_spec_to_code_mode_tool_definition_returns_augmented_nested_tools()

作用:这个测试确认普通自由格式工具能被转换成代码模式里的 ToolDefinition,也就是模型实际会看到的工具定义。它还确认转换时会自动补上可调用声明。

数据流:进去的是一个名叫 apply_patch 的自由格式工具,原本只说明“Apply a patch”,并带有 patch 语法。测试把它传给 tool_spec_to_code_mode_tool_definition。出来应该是 Some,也就是“成功生成了一个工具定义”:名字仍是 apply_patch,工具名被规范化,类型标成 Freeform,输入输出结构为空,同时 description 后面追加了 apply_patch(input: string): Promise<unknown> 这样的调用说明。

调用关系:这个测试由测试运行器执行。它先构造一个 Freeform 工具样本,再检查转换结果;最后通过 assert_eq! 确认生成的 ToolDefinition 和预期完全一致。它覆盖的是“可嵌入代码模式的普通工具应该被纳入工具清单”这条主流程。

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

tool_spec_to_code_mode_tool_definition_skips_unsupported_variants124–137 ↗
fn tool_spec_to_code_mode_tool_definition_skips_unsupported_variants()

作用:这个测试确认不支持的工具种类不会被硬塞进代码模式。这里用 ToolSearch 做例子,预期结果是直接返回 None,表示“这个工具不适合转换”。

数据流:进去的是一个 ToolSearch 工具,包含执行方式、描述和一个空参数结构。测试调用 tool_spec_to_code_mode_tool_definition 后,要求出来的是 None。也就是说,函数没有生成任何代码模式工具定义,也没有假装这个搜索工具可以被代码模式调用。

调用关系:这个函数在测试运行器跑测试时执行。它用 assert_eq! 检查返回值是不是 None,保护的是转换函数的过滤规则:只转换支持的工具类型,不支持的类型要安静跳过,避免后续流程拿到错误工具定义。

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

tools/src/responses_api_tests.rs源码 ↗
testtest run

这个文件专门检查几种工具描述的“翻译结果”。项目里有普通工具、动态工具、MCP 工具,以及按命名空间打包的一组工具。它们最后都要变成 Responses API 能理解的 JSON 形状。这里的测试像验货员:先造一个小工具,比如“按订单号查订单”,再调用真正的转换函数,最后把结果和期望答案逐项对比。一个重点是 defer_loading,意思是“先只告诉模型有这个工具,真正细节晚点再加载”。测试确认 false 不会被多余写出来,而 true 必须保留下来。最后还检查命名空间里的子工具序列化成 JSON 后,是否带着正确的 type、name、description、parameters 等字段。

函数细节4
tool_definition_to_responses_api_tool_omits_false_defer_loading17–49 ↗
fn tool_definition_to_responses_api_tool_omits_false_defer_loading()

作用:这个测试确认普通工具转换成 Responses API 工具时,如果 defer_loading 是 false,就不要把它写成一个显式字段。这样生成的接口内容更干净,也符合预期格式。

数据流:进去的是一个手写的 ToolDefinition,里面有工具名、说明、输入参数结构、输出结构,以及 defer_loading=false。测试把它交给 tool_definition_to_responses_api_tool 转换,然后用 assert_eq! 把实际结果和预先写好的 ResponsesApiTool 比较。出来的结果应该是 defer_loading 为 None,也就是最终格式里不主动声明这个 false。

调用关系:它由 Rust 测试框架在跑测试时自动调用。它主要验证 tool_definition_to_responses_api_tool 这个转换入口的一个边界行为,最后把判断交给 assert_eq!,如果字段多了或少了,测试就会失败。

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

dynamic_tool_to_responses_api_tool_preserves_defer_loading52–85 ↗
fn dynamic_tool_to_responses_api_tool_preserves_defer_loading()

作用:这个测试确认动态工具转换后,会保留 defer_loading=true。动态工具的定义通常来自运行时,如果这个标记丢了,系统可能会过早加载本该延迟加载的工具细节。

数据流:进去的是一个 DynamicToolFunctionSpec,里面的输入结构用 JSON 写出,比如 order_id 是必填字符串,并且 defer_loading=true。测试调用 dynamic_tool_to_responses_api_tool,把 JSON 参数结构转换成项目内部的 JsonSchema,再生成 ResponsesApiTool。出来的结果应该保留 name、description、参数结构,并且 defer_loading 是 Some(true),输出结构为空。

调用关系:它由测试框架自动执行,用来盯住 dynamic_tool_to_responses_api_tool 的转换结果。测试里用 json! 快速搭出输入 JSON,用 assert_eq! 检查转换后的完整对象是不是和预期一模一样。

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

mcp_tool_to_deferred_responses_api_tool_sets_defer_loading88–124 ↗
fn mcp_tool_to_deferred_responses_api_tool_sets_defer_loading()

作用:这个测试确认 MCP 工具转换成 Responses API 工具时,会被标记为延迟加载。MCP 可以理解成一种让外部程序提供工具的协议,这里要保证这些外部工具不会一上来就把全部内容都展开。

数据流:进去的是一个 rmcp::model::Tool,也就是 MCP 风格的工具对象,包含名字、说明和 JSON 参数结构;同时还传入一个带命名空间的 ToolName,用来说明它来自哪里。测试调用 mcp_tool_to_deferred_responses_api_tool,期望出来的是普通 ResponsesApiTool,名字变成 lookup_order,参数结构被解析好,并且 defer_loading 被设置成 Some(true)。

调用关系:它由测试框架在测试阶段调用,覆盖 MCP 工具进入 Responses API 之前的那条转换通道。它先用 new 和 object 构造 MCP 工具,再把检查交给 assert_eq!,确保 mcp_tool_to_deferred_responses_api_tool 没有忘记设置延迟加载标记。

调用图:外部调用 5 个(assert_eq!, json!, new, object, new)。

loadable_tool_spec_namespace_serializes_with_deferred_child_tools127–168 ↗
fn loadable_tool_spec_namespace_serializes_with_deferred_child_tools()

作用:这个测试确认“命名空间里的工具列表”转成 JSON 时,里面的子工具如果是延迟加载,也会正确写出 defer_loading=true。命名空间可以理解成一个工具文件夹,里面放着一组相关工具。

数据流:进去的是一个 LoadableToolSpec::Namespace,里面有命名空间名称、说明,以及一个 create_event 子工具。测试用 serde_json::to_value 把它序列化成 JSON 值,然后和手写的 JSON 对比。出来的 JSON 必须包含 type=namespace,子工具必须包含 type=function、工具名、说明、strict=false、defer_loading=true,以及 parameters 的对象结构。

调用关系:它由测试框架自动运行,主要检查序列化这一步,而不是单个转换函数。它把 LoadableToolSpec::Namespace 和内部的 ResponsesApiTool 组合起来,再交给 serde_json::to_value 生成 JSON,最后用 assert_eq! 确认对外发出的形状完全正确。

调用图:外部调用 4 个(assert_eq!, to_value, Namespace, vec!)。

tools/tests/json_schema_policy_fixtures.rs源码 ↗
testtest run

很多外部工具会用 JSON Schema(一种用 JSON 写的“参数说明书”)告诉系统自己需要哪些输入。这个文件用 Slack、Google Calendar、Notion 等真实风格的样例,检查这些说明书被转换后是否还像原来那样可用。它先从 fixture(固定测试样本文件)里读出工具名称、描述、输入 schema,以及哪些字段应该保留、哪些应该删掉。然后把 MCP 工具转换成 Responses API 工具,再逐项核对:名字和描述要不变,参数必须还是对象形状,指定的重要字段要保留, unreachable definition(已经用不到的定义)和不支持的字段要被清掉。它还专门测试一个特别大的 Notion schema,确认系统会压缩它:先删描述,再必要时删根部定义,但仍保留顶层参数的大致形状。没有这些测试,转换逻辑可能看起来能跑,却在真实工具上生成又大又错、接口不接受的参数说明。

函数细节5
json_schema_policy_fixtures_convert_to_responses_tools48–119 ↗
fn json_schema_policy_fixtures_convert_to_responses_tools()

作用:这个测试会逐个读取一批真实风格的工具样本,检查转换后的 Responses API 工具是否符合预期。它主要防止转换过程把工具名字、描述、参数结构,或者应该保留的 schema 内容弄坏。

数据流:进去的是固定路径里的多个 fixture 文件;测试把每个文件读成结构化数据,再把里面的每个工具交给 convert_fixture_tool 转成 Responses API 工具。随后它把转换后的参数重新变成 JSON 值,逐项比较:名字、描述、strict:false、type:object、properties 对象是否正确;指定路径上的值是否还在;应该剪掉或丢掉的字段是否已经消失。出来的不是业务结果,而是测试通过或失败;失败时会告诉是哪一个工具、哪一个字段不符合预期。

调用关系:它是这组普通 fixture 的主验收流程。它自己不直接懂怎么转换工具,而是把转换交给 convert_fixture_tool;断言和 JSON 构造则交给 assert、assert_eq、json、to_value 这些测试和序列化工具完成。

调用图:调用 1 个内部函数(convert_fixture_tool);外部调用 4 个(assert!, assert_eq!, json!, to_value)。

json_schema_policy_oversized_golden_schema_triggers_compaction122–184 ↗
fn json_schema_policy_oversized_golden_schema_triggers_compaction()

作用:这个测试专门检查一个特别大的 Notion 输入 schema 是否会触发“瘦身”。它确保系统不是简单粗暴地删东西,而是在变小的同时保住关键参数形状。

数据流:进去的是一个超大的 Notion fixture 文件路径;它用 load_fixture 读出样本,取第一个工具,先用 compact_json_len 算原始 schema 的压缩后字节数。然后用 convert_fixture_tool 转换,再把转换后的参数转成 JSON,重新计算字节数。测试要求输出必须比输入更小;同时检查根描述、嵌套描述和根部定义已经消失,还检查 parent、children、markdown、properties 等关键位置仍有合理的结构和值。出来的是测试结论:压缩策略是否既减小体积又没有破坏主要参数。

调用关系:它是大 schema 的专项验收流程。它会调用 load_fixture 取得样本,调用 compact_json_len 比较前后大小,调用 convert_fixture_tool 执行真实转换,再用断言确认压缩后的内容符合预期。

调用图:调用 3 个内部函数(compact_json_len, convert_fixture_tool, load_fixture);外部调用 4 个(assert!, assert_eq!, json!, to_value)。

load_fixture186–190 ↗
fn load_fixture(path: &str) -> T

作用:这个辅助函数负责把测试样本文件读进来,并解析成测试代码能使用的 Rust 数据结构。它让测试不用每次重复写“找文件、读文本、解析 JSON”这几步。

数据流:进去的是一个 fixture 文件路径字符串;它先用 find_resource 找到项目里的真实资源文件位置,再用 read_to_string 把文件内容读成文字,最后用 from_str 把 JSON 文本解析成调用者指定的类型。出来的是解析好的 fixture 数据;如果路径找不到、文件读不了、JSON 格式不对,测试会直接失败并给出说明。

调用关系:它是测试读取样本的入口小工具。在当前调用关系里,超大 schema 测试 json_schema_policy_oversized_golden_schema_triggers_compaction 会直接用它;普通 fixture 测试也通过同样的加载思路读取样本,用来驱动后面的转换检查。

调用图:被 1 处调用(json_schema_policy_oversized_golden_schema_triggers_compaction);外部调用 3 个(find_resource!, read_to_string, from_str)。

convert_fixture_tool192–210 ↗
fn convert_fixture_tool(
    fixture: &FixtureFile,
    fixture_tool: &FixtureTool,
) -> codex_tools::ResponsesApiTool

作用:这个辅助函数把 fixture 里的工具描述,包装成 MCP 工具,再调用真正的转换函数生成 Responses API 工具。它把测试样本和生产转换逻辑接在一起。

数据流:进去的是整个 fixture 文件信息和其中一个工具。它取出工具名、描述和 input_schema,把 input_schema 确认为 JSON 对象后复制出来;然后创建一个 MCP Tool,并用来源名加工具名组成带命名空间的 ToolName,最后调用 mcp_tool_to_responses_api_tool 做实际转换。出来的是 ResponsesApiTool;如果样本里的 schema 不是对象,或转换失败,测试会立刻报错。

调用关系:它处在所有测试和真实转换逻辑之间。两个测试 json_schema_policy_fixtures_convert_to_responses_tools 和 json_schema_policy_oversized_golden_schema_triggers_compaction 都会调用它;它再把工作交给 ToolName::namespaced、rmcp 的 Tool::new,以及 codex_tools 里的 mcp_tool_to_responses_api_tool。

调用图:调用 1 个内部函数(namespaced);被 2 处调用(json_schema_policy_fixtures_convert_to_responses_tools, json_schema_policy_oversized_golden_schema_triggers_compaction);外部调用 3 个(new, mcp_tool_to_responses_api_tool, new)。

compact_json_len212–216 ↗
fn compact_json_len(value: &Value) -> usize

作用:这个辅助函数计算一个 JSON 值在“紧凑写法”下占多少字节。它用于判断 schema 转换后是不是真的变小了。

数据流:进去的是一个 serde_json::Value,也就是内存里的 JSON 值;它用 to_vec 把这个值序列化成没有多余空格的 JSON 字节数组,然后返回数组长度。出来的是字节数;如果这个 JSON 值无法序列化,测试会失败。

调用关系:它只服务于超大 schema 测试 json_schema_policy_oversized_golden_schema_triggers_compaction。那个测试先用它量原始输入大小,再量转换输出大小,最后用这两个数字判断压缩是否生效。

调用图:被 1 处调用(json_schema_policy_oversized_golden_schema_triggers_compaction);外部调用 1 个(to_vec)。

端到端命令应用

最后一组包含仓库级端到端测试,用于将任务生成的 diff 应用到 Git 工作树。

chatgpt/tests/suite/apply_command_e2e.rs源码 ↗
testtest run

这是一组端到端测试,也就是尽量像真实用户那样把整条流程跑一遍。它先临时建一个干净的 Git 仓库,做一次初始提交,避免测试污染开发者自己的目录。然后它从固定的测试数据文件里读出一个任务响应,这个响应里包含要应用的代码改动。第一个测试验证:调用 apply_diff_from_task 后,仓库里应该出现 scripts/fibonacci.js,而且内容、行数都符合预期。第二个测试故意先放一个不同版本的 fibonacci.js,再应用同一份改动,模拟两个人改了同一个地方的情况。这里预期不是成功,而是产生合并冲突。可以把它理解成:先搭一个临时舞台,再拿固定剧本演一遍,确认演员在顺利场景和撞车场景下都表现正确。

函数细节4
create_temp_git_repo8–68 ↗
async fn create_temp_git_repo() -> anyhow::Result<TempDir>

作用:这个函数搭建一个临时 Git 仓库,专门给测试使用。这样测试可以随便创建、提交、改文件,不会弄脏真实项目目录。

数据流:进去时没有业务输入,只依赖系统里的 git 命令和临时目录能力。它先创建临时文件夹,再执行 git init,设置测试用的用户名和邮箱,写入 README.md,加入 Git 并提交成初始版本。出来的是一个 TempDir,也就是会自动清理的临时目录;如果 Git 初始化或提交失败,就返回错误。

调用关系:它是两个测试的准备步骤。test_apply_command_creates_fibonacci_file 和 test_apply_command_with_merge_conflicts 都先调用它,拿到一个像真实项目一样的 Git 仓库,然后再把待测的 apply_diff_from_task 放进去运行。

调用图:被 2 处调用(test_apply_command_creates_fibonacci_file, test_apply_command_with_merge_conflicts);外部调用 5 个(new, bail!, new, write, vec!)。

mock_get_task_with_fixture70–75 ↗
async fn mock_get_task_with_fixture() -> anyhow::Result<GetTaskResponse>

作用:这个函数从测试夹具文件里读取一份假的任务响应。夹具就是固定的测试样本,用它可以让测试每次都拿到同样的输入。

数据流:进去时不需要参数。它通过 find_resource! 找到 tests/task_turn_fixture.json,异步读取这个 JSON 文本,再用 serde_json 把文本解析成 GetTaskResponse 结构。出来的是一份内存里的任务响应;如果文件找不到、读取失败或 JSON 格式不对,就返回错误。

调用关系:它给两个测试提供同一份“任务内容”。测试不去真的请求网络服务,而是用这份固定文件假装服务已经返回了任务,这样 apply_diff_from_task 可以被稳定、可重复地检查。

调用图:被 2 处调用(test_apply_command_creates_fibonacci_file, test_apply_command_with_merge_conflicts);外部调用 3 个(find_resource!, from_str, read_to_string)。

test_apply_command_creates_fibonacci_file78–117 ↗
async fn test_apply_command_creates_fibonacci_file()

作用:这个测试检查最顺利的情况:任务里的补丁被应用后,仓库里真的生成了预期的 fibonacci.js 文件。它证明 apply 命令不是只返回成功,而是真的把文件写对了。

数据流:开始时它先创建一个临时 Git 仓库,再读取固定任务响应。接着把任务响应和仓库路径交给 apply_diff_from_task。之后它检查 scripts/fibonacci.js 是否存在,读取文件内容,确认里面有 fibonacci 函数、有 Node 脚本开头、有模块导出,并且总行数是 31 行。测试本身没有返回业务数据;如果任何一步不符合预期,就让测试失败。

调用关系:它是正常路径的端到端验证。它调用 create_temp_git_repo 准备仓库,调用 mock_get_task_with_fixture 准备任务,然后把核心工作交给 apply_diff_from_task,最后用断言检查结果是否真的落到了磁盘上。

调用图:调用 3 个内部函数(apply_diff_from_task, create_temp_git_repo, mock_get_task_with_fixture);外部调用 3 个(assert!, assert_eq!, read_to_string)。

test_apply_command_with_merge_conflicts120–188 ↗
async fn test_apply_command_with_merge_conflicts()

作用:这个测试检查出问题的情况:仓库里已经有一个不同版本的 fibonacci.js 时,再应用任务里的改动应该触发合并冲突。它确认程序不会假装成功覆盖掉用户已有内容。

数据流:它先创建临时 Git 仓库,然后手动建 scripts 目录,写入一个和任务补丁不一致的 fibonacci.js,并把这个文件提交到 Git。接着它临时把当前工作目录切到测试仓库,读取固定任务响应,再调用 apply_diff_from_task。预期结果是返回错误。最后它重新读取 fibonacci.js,检查文件里是否出现 Git 冲突标记,比如 <<<<<<<、=======、>>>>>>>。同时它用 DirGuard 在测试结束时把当前目录切回原来的位置。

调用关系:它是异常路径的端到端验证。它同样依赖 create_temp_git_repo 和 mock_get_task_with_fixture 做准备,但会额外制造一个冲突文件。随后它调用 apply_diff_from_task,并确认这个核心函数在冲突场景下把失败暴露出来,而不是悄悄覆盖或误报成功。

调用图:调用 3 个内部函数(apply_diff_from_task, create_temp_git_repo, mock_get_task_with_fixture);外部调用 7 个(assert!, new, current_dir, set_current_dir, create_dir_all, read_to_string, write)。