ente-test-support 集成测试基础设施:在 Rust 测试中一键拉起 Museum、临时 Postgres 与本地对象存储
【免费下载链接】ente💚 End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente
导读
ente 仓库的 Rust 侧提供了一批针对后端 Museum 的集成测试,而ente-test-support(源码位于 rust/crates/test-support)正是承载这些测试的轻量级测试基础设施:它能在测试进程内自动启动一个由临时 Postgres 与本地内存对象存储支撑的 Museum 实例,并通过 Cargo feature 门控,让普通cargo test默认跳过、按需开启。读完本文,你将掌握这套测试装置的工作原理、环境依赖、运行命令,以及如何在其上编写自己的端到端测试。
它解决什么问题
Museum 是 ente 的后端服务(见 server),其正常运行需要依赖数据库与对象存储。要让 Rust 集成测试真正覆盖"账号注册、同步、导出、分享"等全链路行为,测试环境就必须具备:
- 一个真实可用的 Museum HTTP 服务;
- 一个可读写的关系型数据库(Postgres);
- 一个兼容 S3/B2 语义的对象存储。
ente-test-support的价值在于把这些外部依赖全部"内嵌"进测试进程:Postgres 由 postgresql_embedded 按需下载并管理,对象存储则是一个运行在线程内的极简 HTTP 服务器,Museum 二进制由go build现场编译。整个环境是临时的、进程内的、随测试销毁的,无需开发者预先安装 Postgres、配置 S3 或准备任何密钥。
环境准备与依赖
按照 rust/crates/test-support/README.md 的说明,运行这类测试只需满足两点:
- PATH 中必须有
go命令——用于编译并启动 Museum。server.rs中的require_go()会先执行go version校验,失败时直接返回 "Museum live tests requiregoon PATH"(rust/crates/test-support/src/server.rs); - Postgres 二进制无需手动安装,
postgresql_embedded会在首次使用时自动下载并缓存。
关于缓存位置,值得展开说明:postgresql_embedded默认把二进制放在$HOME/.theseus,而 postgres.rs 中的install_dir()将其重定向到系统约定的缓存目录下的.theseus/postgresql(例如 Linux 上为$XDG_CACHE_HOME或~/.cache/.theseus/postgresql),避免污染家目录根级。依赖配置见 Cargo.toml:postgresql_embedded = "0.20.4"启用了blocking、rustls、theseus三个 feature,同时使用tokio(rt-multi-thread)、uuid(v4)与dirs。该 crate 标记为publish = false,属于仓库内部的专用测试库,不对外发布。
架构组成:四个协同模块
src/lib.rs 对外只暴露Museum类型与少量常量,内部按职责拆分为四个模块:
| 模块 | 职责 |
|---|---|
| postgres.rs | 管理临时 Postgres 实例,创建测试数据库ente_test |
| object_store.rs | 线程内运行的极简 HTTP 对象存储(内存版 S3) |
| server.rs | 编译并启动 Museum,注入全部环境变量,等待就绪 |
| process.rs | 子进程封装:日志落盘、状态检查、Drop 时回收 |
临时 Postgres
postgres.rs 的start()构造Settings时使用temporary: true、随机空闲端口(free_port()),启动后调用create_database("ente_test")创建专用数据库,并通过host()/port()/username()/password()/database()向外部暴露连接参数。
内存对象存储
object_store.rs 用TcpListener绑定随机端口,在后台线程里循环 accept 连接,实现了一个最小可用的对象存储协议:
PUT记录对象路径与长度,返回200 OK;HEAD按路径返回200(带 Content-Length)或404 Not Found;- 其他方法一律返回
405 Method Not Allowed。
对象数据存放在线程内的HashMap<String, usize>中(只记大小、不落盘),足以支撑 Museum 上传/探测对象的逻辑。
Museum 的编译与启动
server.rs 的start()做了三件事:
- 定位 server 目录:由
CARGO_MANIFEST_DIR(即本 crate 目录)向上回溯三层得到仓库根目录,再拼接server,对应仓库根下的 server 目录; - 编译 Museum:执行
go build -o <临时目录>/museum ./cmd/museum; - 注入环境变量并启动:通过
Command为 Museum 进程设置全套ENTE_*环境变量。
就绪探测与日志
启动后wait_for_museum()会在90 秒内以500ms间隔轮询GET /ping,直到返回 HTTP 200 才认为就绪;超时或进程提前退出时,会把保存在临时目录logs/museum.log中的日志摘要一并抛出,便于排查(见 server.rs)。子进程在 Drop 时会被 kill 并回收(process.rs)。
环境变量注入清单
Museum 通过环境变量读取配置。server.rs的start()注入的变量是理解整套测试装配的关键,归纳如下:
数据库连接(指向临时 Postgres)
| 变量 | 值 |
|---|---|
ENTE_DB_HOST/ENTE_DB_PORT | 临时 Postgres 地址 |
ENTE_DB_NAME | ente_test |
ENTE_DB_USER/ENTE_DB_PASSWORD | 临时实例的账号密码 |
ENTE_DB_SSLMODE | disable |
对象存储(指向内存 S3)
| 变量 | 值 |
|---|---|
ENTE_S3_ARE_LOCAL_BUCKETS | true,声明桶为本地的,不校验真实云凭证 |
ENTE_S3_B2_EU_CEN_KEY/ENTE_S3_B2_EU_CEN_SECRET | changeme/changeme1234测试占位凭证 |
ENTE_S3_B2_EU_CEN_ENDPOINT | 内存对象存储地址 |
ENTE_S3_B2_EU_CEN_REGION | eu-central-2 |
ENTE_S3_B2_EU_CEN_BUCKET/ENTE_SPACE_ASSETS_PRIMARYBUCKET | b2-eu-cen |
HTTP 与测试辅助
| 变量 | 值 |
|---|---|
ENTE_HTTP_PORT | 随机空闲端口,测试据此得到 endpoint |
ENTE_CREDENTIALS_FILE | 指向一个空文件(见下文"空配置文件的用意") |
ENTE_INTERNAL_HARDCODED_OTT_LOCAL_DOMAIN_SUFFIX | @example.org |
ENTE_INTERNAL_HARDCODED_OTT_LOCAL_DOMAIN_VALUE | 123456 |
ENTE_JOBS_CRON_SKIP | true,跳过定时任务,保证测试确定性 |
其中HARDCODED_OTT = "123456"与HARDCODED_OTT_EMAIL_SUFFIX = "@example.org"定义在 src/lib.rs:任何以@example.org结尾的测试邮箱都可以直接用固定 OTP123456完成登录,从而绕开真实邮件发送链路。
空配置文件的用意
write_config()特意写一个空文件作为ENTE_CREDENTIALS_FILE,源码注释说明了原因:不能在这里写任何配置,否则会覆盖开发者本地的museum.yaml;而空文件本身又能"屏蔽"掉任何真实的本地凭据文件,防止测试意外连到生产/开发环境。
如何使用:Cargo feature 门控与运行命令
按 README 约定,使用该装置的测试统一通过名为museum的 Cargo feature 门控。例如 CLI crate 的 Cargo.toml 中声明了museum = [],对应集成测试文件(如 rust/apps/cli/tests/sync.rs)首行就是#![cfg(feature = "museum")]——意味着不启用该 feature 时,普通cargo test会直接跳过这些测试,不影响日常开发。
启用 feature 后运行:
cargo test -p ente-rs --features museum(以上命令即 rust/crates/test-support/README.md 中给出的标准调用方式。)从源码结构看,当前仓库中使用ente-test-support的集成测试还包括:
- rust/apps/cli/tests/paste.rs、rust/apps/cli/tests/sync.rs(CLI 的粘贴与同步全链路);
- rust/crates/accounts/tests/accounts.rs、rust/crates/contacts/tests/contacts.rs、rust/crates/space/tests/space.rs(账号、联系人、Space 等模块的集成测试)。
Museum::run 与 run_async
src/museum.rs 提供两个入口:
Museum::run(|museum| ...):同步版本,闭包接收&Museum,内部用catch_unwind包裹测试体,任何错误或 panic 都会触发temp_dir.retain()——保留临时目录并打印路径,方便事后检查日志与数据;Museum::run_async(|endpoint| ...):异步版本,内部新建 tokio runtime 并block_on,闭包直接拿到endpoint字符串。
Museum对外暴露两个方法:endpoint()返回http://127.0.0.1:<port>形式的服务地址,temp_dir()返回临时工作目录。临时目录以ente-test-<uuid>命名,正常结束时随 Drop 自动删除(src/museum.rs 中的TempDir)。
一个真实的测试示例
以 rust/apps/cli/tests/sync.rs 为例,可以看到完整的用法模式:
#[test] fn sync() -> TestResult { Museum::run(|museum| { let cli = support::cli_session(museum, "sync")?; let export_dir = museum.temp_dir().join("export"); std::fs::create_dir_all(&export_dir)?; cli.run_ok(&[ "account", "create", "--email", "sync-test@example.org", "--password", "sync-test-password", "--endpoint", museum.endpoint(), "--export-dir", export_dir.to_str().unwrap(), "--otp", HARDCODED_OTT, // 123456 ])?; let output = cli.run_ok(&["export"])?; assert!(output.contains("Sync completed"), "export did not sync: {output}"); Ok(()) }) }这个测试在 5 行内完成了"启动全套后端 → 注册账号(用固定 OTP 验证)→ 执行导出同步 → 断言输出"的完整闭环,充分体现了这套基础设施对集成测试的简化能力。
测试账号夹具与常量
src/lib.rs 还内置了一个account_fixture模块,供需要"预置账号密钥"的测试复用:
PASSWORD = "museum-account-fixture-password";KEK(密钥加密密钥)与其KEK_SALT为预计算的固定值,注释说明其派生参数为 Argon2id(密码, 16 × 0x4d 盐, 256 MiB 内存, 16 次迭代);MEM_LIMIT = 268_435_456(256 MiB)、OPS_LIMIT = 16,与 Argon2id 参数一一对应。
使用预计算 KEK 可以让涉及加密导出的测试跳过昂贵的密钥派生过程,同时保证加解密结果可预期。
运行前提与注意事项
- 必须启用
museumfeature,否则相关测试被#![cfg(feature = "museum")]直接裁剪,表现为"测试不存在"而非"测试失败"; - 首次运行会下载 Postgres 二进制(缓存在缓存目录的
.theseus/postgresql下),需要网络与磁盘空间,之后运行不再重复下载; - 需要
go工具链编译 Museum(对应 server 目录下的cmd/museum),编译产物写入测试临时目录,不做全局安装; - 端口全部动态分配:Postgres 端口、对象存储端口、Museum HTTP 端口均通过
bind(0)获取随机空闲端口(net.rs),多测试并行时不会冲突; - 环境隔离:测试通过环境变量注入配置且使用空
ENTE_CREDENTIALS_FILE,不会读取开发者本地的museum.yaml或真实云凭证; - 失败可诊断:一旦 Museum 未就绪、进程提前退出或测试失败,临时目录会被保留(终端会打印
retaining integration test temp dir: <路径>),其中logs/museum.log是首选的排障入口。
小结
ente-test-support是一个"小而美"的测试基础设施:它以极少的对外 API(Museum::run/run_async、endpoint()、temp_dir()、HARDCODED_OTT、account_fixture)封装了"编译 Go 后端 + 下载托管 Postgres + 内存对象存储 + 子进程生命周期管理"的全部复杂度。理解它的环境变量注入清单与临时目录保留策略,是你在 rust/apps/cli/tests 与 rust/crates 各模块测试目录下阅读、扩展或新写集成测试时最重要的前置知识。
【免费下载链接】ente💚 End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考