Firecracker 集成测试体系深入指南:从 pytest 运行、A/B 回归对比到调试实战
本文系统性讲解 Firecracker(secure and fast microVMs for serverless computing)仓库内置的整套集成测试体系。这套体系以 pytest 为核心、以 tools/devtool 为统一入口,覆盖功能、性能与安全契约,并配套 Rust 原生集成测试、Sanitizer 构建测试和用于性能回归的 A/B-Testing 框架。读完本文,你将掌握 Firecracker 集成测试的完整运行方式、fixture 与标记的使用规则、如何编写和调试测试,以及如何利用 A/B 测试在不依赖“ground truth”的前提下做性能与安全回归分析。
测试体系的定位:守护安全、质量与性能契约
tests/ 目录下的所有测试,其使命是"uphold the security, quality, and performance contracts of Firecracker"——即守护 Firecracker 的安全、质量与性能三大约定。测试代码分布在仓库的 tests/(Python 集成测试)、src/vmm/tests/(Rust 集成测试)与 .buildkite/(CI 流水线生成)三个层面,配合测试产物(guest 内核与 rootfs,见 tests/data)共同工作。
整体设计原则(详见 tests/README.md 的 "Implementation Goals" 一节)可以概括为三条:
- 手动与 CI 两用:既能在开发机上轻松手动运行,也能无缝运行在持续集成环境;
- 测试相互独立且自包含:每个测试期望一个干净的环境,并保证在结束后留下干净环境(超时退出);
- 永远使用最新依赖与资源:测试运行时拉取最新依赖,避免因陈旧资源产生偏差。
快速起步:用 devtool 运行测试
测试系统建立在 pytest 之上,而 tools/devtool 是一个便捷包装脚本:它会自动从 S3 下载所需的测试产物(guest 内核、rootfs 等),随后在 Docker 容器内调用 pytest。tools/devtool help 提供全部用法说明,test 命令的具体参数见 devtool 的 help 输出。
环境依赖
运行完整测试套件前需要确认主机满足:
- 一台 裸金属 Linux 主机,
uname -r>= 5.10,且 KVM 已启用(存在/dev/kvm设备节点); - Docker;
- awscli version 2(用于下载 S3 上的测试产物)。
这些依赖意味着虚拟化层套娃(如 VM 里跑测试)一般不可行——测试需要直接操作 KVM 与宿主机资源。
运行 PR CI 同款的全量测试
运行所有在 PR CI 中执行的测试(即排除带 pytest.mark.nonci 标记的用例):
tools/devtool -y test
-y(--unattended)表示无人值守运行,自动对一切确认提示回答 yes。
运行性能测试
部分性能测试要求对宿主机做性能调优(例如为 hugetlbfs 预留内存)。运行这类测试必须额外加 --performance 标志,它会调整宿主机电源状态(C-state、P-state 等)以获得一致的性能表现:
tools/devtool -y test --performance
定向运行:目录、文件与单个测试
只运行特定目录或文件中的测试时,将 -- 之后的内容原样透传给 pytest(注意:所有路径都相对 tests 目录,而非仓库根目录):
tools/devtool -y test -- integration_tests/performance/test_boottime.py
# 或
tools/devtool -y test --performance -- integration_tests/performance/test_huge_pages.py
运行文件中的某一个测试函数,使用 pytest 的 :: 语法定位:
tools/devtool -y test -- integration_tests/performance/test_boottime.py::test_boottime
用 -k 按名称子串筛选
pytest 的 -k 选项允许按测试名称中的子串过滤。这在指定测试函数的参数化(parametrize)取值时尤其有用,例如下面的命令会运行所有 microVM 内存为 1024MB 的 boottime 测试:
tools/devtool -y test -- -k 1024 integration_tests/performance/test_boottime.py::test_boottime
不使用 devtool:直接在容器或本机跑 pytest
如果不想借助 devtool 的能力,可在容器内直接运行 pytest(先进入 dev 容器再执行):
tools/devtool -y shell -p
pytest [<pytest argument>...]
也可以在开发机上原生运行:
python3 -m pytest [<pytest argument>...]
注意:Python 集成测试要求 root 权限——tests/conftest.py 在会话启动时即校验 os.geteuid() == 0,否则直接抛 PermissionError。
通过 FC_TEST_* 环境变量调整测试行为
测试框架运行时会读取少量 FC_TEST_* 环境变量,它们被 tools/devtool 加入 allowlist 并自动转发进 dev 容器(tools/devtool 中通过 env | grep -P "^(AWS_EMF_|BUILDKITE|CODECOV_|FC_TEST_)" 收集并注入容器环境)。因此在宿主机上直接设置即可生效:
| 环境变量 | 作用 |
|---|---|
FC_TEST_SKIP_ARTIFACT_COPY=1 |
跳过把 CI 产物拷贝到容器内 /srv/test_artifacts 的步骤(devtool test_debug 使用)。对应逻辑见 tools/test.sh:置位后仅创建空目录。 |
FC_TEST_DUMP_ON_FAILURE=1 |
测试失败时额外收集"重型"事后产物:完整内存快照(post_failure.mem + post_failure.vmstate)、测试用 id_rsa 私钥副本,以及 uVM chroot 根目录下的每个普通文件。默认关闭;轻量产物(host-dmesg.log 与 guest 串口控制台日志)则总是被收集。nightly 性能流水线会开启此选项。收集逻辑位于 tests/conftest.py 的 microvm_factory teardown。 |
FC_TEST_DEVELOPMENT_ENVIRONMENT=1 |
跳过那些依赖特定宿主机构型、且在开发环境上运行没有价值的测试。 |
测试输出的去向
测试运行结果与常规输出进入 stdout,错误进入 stderr。默认情况下,测试执行过程中 stdout/stderr 会被捕获,只有测试失败时才会在最终的失败报告里打印。想要边跑边看实时输出(无论成败),加 -s 标志:
tools/devtool -y test -- -s
pytest 的详细行为由 tests/pytest.ini 控制,例如:默认 addopts 包含 --tb=short -vv --durations=10 --showlocals -m 'not nonci and not no_block_pr',并生成 --json-report --json-report-file=../test_results/test-report.json;默认单测超时为 300 秒;pytest 缓存目录被重定向到 ../build/pytest_cache,避免污染源码树。
三层测试体系:Python、Rust 与 Sanitizer
Firecracker 的测试并非只有 Python 集成测试一层。文档将测试划分为三个互补层次。
Python 集成测试(HTTP API 驱动)
pytest 驱动的集成测试通过 Firecracker 的 HTTP API 来配置 VMM 并与之通信——即把 Firecracker 当作一个运行中的服务来"黑盒"验证。这一层覆盖了功能(tests/integration_tests/functional)、性能(tests/integration_tests/performance)与安全(tests/integration_tests/security)三大类用例。
Rust 集成测试(程序化 API 驱动)
与 HTTP 集成测试并行,vmm crate 自带一批原生 Rust 集成测试(见 src/vmm/tests,入口为 integration_tests.rs),它们不经过 HTTP,直接通过程序化 API(例如 build_and_boot_microvm、PrebootApiController、RuntimeApiController)驱动 VMM。cargo test 时 Cargo 会自动收集这些测试,它们同样计入代码覆盖率。
只运行 Rust 集成测试:
cargo test --test integration_tests --all
与单元测试不同,Rust 集成测试每个都运行在独立的进程中,且 Cargo 会将它们打包进一个新的 crate,这带来两个已知的副作用:
-
只能调用
pub函数——这恰好使 VMM 能以"程序化用户"的视角被消费。如果某个函数必须被测试使用却还不是pub,在把它加入公共接口之前,请慎重思考它是否在概念上真的需要暴露。 -
VMM 正确退出的前提是 exit code 0(这是资源正确清理所必需的);但 Cargo 并不期望测试进程自行结束,因此无法正常收集其输出。实际输出形态大致是:
cargo test --test integration_tests running 3 tests test test_setup_serial_device ... ok
从源码看(integration_tests.rs),test_build_and_boot_microvm 会先断言"未配置 boot source 时报 MissingKernelConfig 错误",再对 pci_enabled 与 memory_hotplug 的各组合逐一构建并引导启动一个 microVM,最后断言其以 FcExitCode::Ok 退出。这类测试同样贯彻了"可独立、自包含"的设计目标。
Sanitizer 构建测试
Firecracker 还设有一个专门的构建测试,在 sanitizer 下运行 Rust 集成测试:
tools/devtool -y test -- -m nonci integration_tests/build/test_sanitizers.py
注意这里显式用了 -m nonci 来覆盖 pytest.ini 的默认标记过滤。对应的 Buildkite sanitizer 流水线由 .buildkite/pipeline_sanitizers.py 生成。
A/B 测试:一种不需要 ground truth 的回归策略
A/B-Testing 是一种将某个测试函数在两个不同环境(A 与 B)中分别执行、再对两次输出做比较的测试策略。它的优势在于不需要在仓库里固化一份 ground truth——基准答案由 A 环境动态生成。Firecracker 的 A/B 测试通常比较两个不同 commit 编译出的二进制(例如 A 是 main 分支 HEAD,B 是针对 main 的 PR 分支 HEAD)。
采用 A/B 策略的场景是 ground truth 出现了这两种情况之一:
- 会因代码库外部因素而改变:例如安全测试——若某个依赖被发布了 CVE,相关测试就会失败;
- 过于复杂或变化过快,不适合固化在代码库中:例如大而全的性能基准数据。
A/B 测试的核心机制在 tests/framework/ab_test.py 中实现,其中 git_ab_test() 将指定 revision 检出到临时目录并分别执行 test_runner,git_ab_test_host_command_if_pr() 则区分 PR 语境与非 PR 语境——以 cargo audit 测试为例:PR 内运行时比较 main HEAD 与 PR HEAD 的审计输出是否一致;脱离 PR 运行时则直接断言环境自身状态(即任何现存依赖都无公开 RustSec advisory)。
Functional A/B-Tests(功能一致性)
部分功能型 A/B 测试(例如 test_vulnerabilities.py)会对比 PR 目标分支(如 main)与 PR head 的状态。但在本地运行时,pytest 并不知道当前 commit 属于哪个 PR,无法自行完成 A/B 对比。解决办法是伪造一个 PR 环境——设置 BUILDKITE_PULL_REQUEST 与 BUILDKITE_PULL_REQUEST_BASE_BRANCH 两个环境变量:
BUILDKITE_PULL_REQUEST=true BUILDKITE_PULL_REQUEST_BASE_BRANCH=main ./tools/devtool test -- integration_tests/security/test_vulnerabilities.py
Performance A/B-Tests(性能对比)
性能 A/B 测试有专门的长时编排框架,它们不进入 PR 前 CI,而是安排在合并后(post-merge)定时运行。典型代表是 快照恢复延迟测试——这类测试自身不含任何断言,只通过 aws_embedded_metrics 库发射数据序列。当由编排脚本 tools/ab_test.py 执行时,这些数据序列被收集:脚本用两个不同的 Firecracker 二进制各跑一遍测试,将 A、B 两次运行中维度(dimensions)匹配的数据序列一一对应,并对每条序列执行非参数检验;凡差异达到统计显著水平的数据序列,其关联指标会被打印。显著性判定策略可用 tools/ab_test.py --help 配置。
手动执行一个 A/B 测试的完整命令形态为:
tools/devtool -y test --ab [optional arguments to ab_test.py] run --binaries-a <dir A> --binaries-b <dir B> [optional --artifacts-a <name A> --artifacts-b <name B>] --pytest-opts <test specification>
其中 <dir A>、<dir B> 是存放待比较性能的 firecracker 与 jailer 二进制的目录;<name A>、<name B> 是 A/B 运行各自使用的产物名。
用 devtool build 从任意 git 对象(commit SHA、分支、tag 等)编译二进制,产物会放到 build 下的子目录:
tools/devtool -y build --rev main --release
tools/devtool -y build --rev HEAD --release
tools/devtool -y test --no-build --ab -- run --binaries-a build/main --binaries-b build/HEAD --pytest-opts integration_tests/performance/test_boottime.py::test_boottime
如需下载自定义产物,用 ./tools/devtool download_ci_artifacts <S3 URI>...,产物会放入 build/artifacts 目录。
编写 A/B 兼容测试的三个要点
- 每条需要做 A/B 的指标必须发射多个数据点:非参数检验作用在数据序列上而非单个数据点上,单一数据点无法构成序列;
- 在 dimension 集合中放入
performance_testkey 且值为测试名:tools/ab_test.py靠 dimensions 匹配两条数据序列——同名序列只有在 dimensions 完全一致时才配对。当 pytest 把--pytest-opts展开成多个用例时,若不同用例对不同的数据序列使用了相同 dimensions,脚本会失败并打印违规序列名,因此performance_test键是必需的; - 把全部 pytest 参数纳入 dimension 集合:例如一个按 vCPU 数参数化的 boottime 测试,若发射序列的 dimensions 只有
{"performance_test": "test_boottime"},脚本将无法区分不同 microVM 规格的数据序列而把它们合并。务必让每个用例的维度对其自身唯一。
局限:只能对 Rust 代码做 A/B
编排式 A/B 只检测体现在 Firecracker 二进制上的性能差异:tools/ab_test.py 只 checkout 被传入的 revision 并执行 cargo build 生成二进制,不会在被 checkout 的 revision 语境下运行集成测试;A、B 两次运行都从同一个 docker 容器、同一份集成测试代码触发。这意味着无法用它评估"只改 Python 代码"(例如开启日志)的影响——只有 Rust 代码可被 A/B。唯一的例外是工具链差异:若两个 revision 都带 rust-toolchain.toml,脚本会用该 revision 指定的工具链编译,而非容器内预装的工具链。
Buildkite 中的自动化与手动调度
每次合并后,只要 PR 触及任何 Rust 代码,系统就会自动跑 A/B 测试,流水线由 .buildkite/pipeline_perf.py 生成。若要在 Buildkite 手动调度一次 A/B,需要在 "New Build" 弹窗的 "Environment Variables" 字段中设置 REVISION_A 与 REVISION_B。
超越 commit 对比:任意环境间的比较
自动化的 A/B 套件只支持跨 commit 范围,但脚本也可手动用于任意环境的对比——例如同一二进制在不同宿主机上的表现。做法是:先在各环境里用 devtool 以普通(非 A/B)方式运行测试,这会产出包含 metrics.json 的 test_results 目录;然后让 tools/ab_test.py 分析这些目录:
tools/ab_test.py analyze <path to A `test_results`> <path to B `test_results`>
随后输出与前述流程相同的统计分析结果。
生成 A/B 结果可视化(pdf 或 table),使用 tools/ab_plot.py,它消费与 ab_test.py 相同的 metrics.json:
./tools/ab_plot.py <path to A `test_results`> <path to B `test_results`> --output_type pdf
也可通过 devtool 在预装了依赖的 dev 容器里执行:
./tools/devtool sh ./tools/ab_plot.py <path to A `test_results`> <path to B `test_results`> --output_type pdf
注意:对排列组合很多的测试,生成
A/B 故障排查:一个典型报错
若 tools/ab_test.py analyze 报出类似错误:
$ tools/ab_test.py analyze <first test-report.json> <second test-report.json>
Traceback (most recent call last):
File "/firecracker/tools/ab_test.py", line 412, in <module>
data_a = load_data_series(args.report_a)
File "/firecracker/tools/ab_test.py", line 122, in load_data_series
for line in test["teardown"]["stdout"].splitlines():
KeyError: 'stdout'
请检查 AWS_EMF_ENVIRONMENT 与 AWS_EMF_NAMESPACE 是否设置为 local。尤其当数据来自 .buildkite/pipeline_perf.py 生成的 Buildkite 流水线时,务必传入 --step-param env/AWS_EMF_NAMESPACE=local --step-param env/AWS_EMF_SERVICE_NAME=local。
添加 Python 测试:理解 fixture 体系
新测试可以放在 tests/ 下任意(既有或新建的)子目录中,文件按 test_*.py 命名,pytest 即会自动发现。
fixture 的两个层次
pytest 中定义的 fixture 让"构造一台 Firecracker microVM"变得极其简单。测试框架把 fixture 分为两层,README 与 conftest.py 的设计注释 保持一致:
消费型(Consumer)fixture——按 microVM 生命周期从早到晚逐步替你完成初始化,按需选择其一:
| fixture | 已替你完成的初始化 | 你还需要做什么 |
|---|---|---|
uvm |
仅构建(已 chroot)的 microVM | 自行驱动 spawn / basic_config / start |
uvm_configured |
uvm + spawn + basic_config(及适用的 cpu_template) |
添加设备后调用 start |
uvm_booted |
uvm_configured + add_net_iface + start |
直接可 ssh |
uvm_restored |
uvm_booted,然后做快照并从快照恢复 |
针对恢复态做验证 |
uvm_any |
启动态或恢复态,通过 uvm_lifecycle 自动参数化 |
用于需要覆盖两种生命周期的用例 |
维度(Dimension)fixture——控制 microVM 的参数,由上面的消费型 fixture 组合引用,测试也可以直接请求它们。默认值如下:
| fixture | 默认值 / 行为 |
|---|---|
guest_kernel |
自动参数化到所有受支持 guest 内核(列表见 docs/kernel-policy.md) |
rootfs_mode |
"ro"(squashfs,默认)或 "rw"(ext4) |
rootfs |
由 guest_kernel + rootfs_mode 组合出的 rootfs 磁盘路径(5.10 用 Ubuntu 24.04,其余用 Amazon Linux 2023) |
pci_enabled |
自动参数化 True / False |
cpu_template |
默认 None |
huge_pages |
默认 HugePagesConfig.NONE |
vcpu_count / mem_size_mib |
默认 2 / 256 |
用 pin 辅助函数限制维度
要把某个维度限制到特定值或子集,可使用 tests/framework/artifacts.py 与 tests/framework/utils_cpu_templates 提供的辅助函数:
from framework.artifacts import pin_guest_kernel, GUEST_KERNEL_DEFAULT, ACPI_GUEST_KERNELS
@pin_guest_kernel(GUEST_KERNEL_DEFAULT) # 单一内核,用于不依赖 guest 的测试
def test_foo(uvm): ...
@pin_guest_kernel(ACPI_GUEST_KERNELS) # 子集
def test_bar(uvm): ...
pin_guest_kernel 本质上是 pytest.mark.parametrize("guest_kernel", ..., indirect=True) 的封装(见 artifacts.py)。
同样的模式也可用在模块级:pytestmark = pin_guest_kernel(...)。但应谨慎使用模块级 pin:默认优先使用 per-test 装饰器,把 pytestmark 留给那些"文件主题本身就要求该限制"的文件,例如:
- 专测某个特性且该特性本身就依赖特定内核的文件(
test_vmclock.py需要 ACPI;性能热插拔内存测试 因 virtio-mem 成熟度需要内核 >= 6.1); - 专门枚举某个维度的文件(如 test_cpu_all.py——该文件的使命就是把每个 CPU template 都跑一遍);
- 性能测试文件:测量必须基于固定基底,结果才可在时间维度上比较。
对"便利性" pin(例如"本测试与 guest 内核无关,固定到默认内核以加快 CI"),请用 per-test 装饰器。模块级 pytestmark 会悄悄继承到下一个新增的测试上,哪怕那个测试本应覆盖多内核。
关键约束:pytest 的 parametrize 标记不会合并——在同一个维度上同时使用模块级 pytestmark 与 per-test parametrize 会报 "duplicate parametrization" 错误,二选一即可。
推荐做法:不依赖 guest 内核的 Firecracker 功能测试用 @pin_guest_kernel(GUEST_KERNEL_DEFAULT) 保持 CI 快速;涉及 guest 与 Firecracker 交互的测试则保留默认,让每个受支持内核都跑一遍。
大页(huge pages)维度
huge_pages 默认为 HugePagesConfig.NONE(匿名内存)。NONE 与 HUGETLBFS_2MB 的区别只影响性能特征(缺页粒度、UFFD 填充时间、EPT violation 计数),不影响功能正确性。因此,专测内存子系统的性能测试(内存热插拔、balloon 页上报/hinting、快照/UFFD 恢复、EPT 等)应显式在两种变体上参数化:
@pytest.mark.parametrize("huge_pages", HugePagesConfig)
def test_my_perf_thing(uvm_booted, huge_pages):
...
功能测试保持默认即可,无需声明该维度。
Markers:nonci 与 no_block_pr
Firecracker 用两个特殊 pytest marker(注册于 tests/pytest.ini)区分测试的运行语境:
nonci:不进入 PR CI 流水线,而是按各自的 cron 计划在独立流水线中运行(如上面的 Sanitizer 测试);no_block_pr:在"可选"的 PR CI 流水线中运行,该流水线不阻塞 PR 合并。
不带任何 marker 的测试在每个 PR 都会运行,且必须通过才能合并。
添加 Rust 测试
新增 Rust 集成测试只需在 src/vmm/tests/integration_tests.rs 中增加一个标注 #[test] 的函数即可,Cargo 会自动发现并执行。这层测试用于验证 VMM 的程序化 API,与 HTTP 层集成测试互补。
操作 Guest 文件系统
框架提供在 guest 文件系统上读写文件的辅助方法。例如,覆盖 guest 的 init 进程、之后取回一份日志:
def test_with_any_microvm_and_my_init(test_microvm_any):
# [...]
test_microvm_any.slot.fsfiles['mounted_root_fs'].copy_to(my_init, 'sbin/')
# [...]
test_microvm_any.slot.fsfiles['mounted_root_fs'].copy_from('logs/', 'log')
copy_to() 的源路径相对宿主根目录,目标路径相对 mounted_root_fs 根目录;copy_from() 反之。
警告:在 guest 运行期间向 guest 文件系统拷贝文件属于未定义行为。
调试与故障排查实战
只重跑上次失败的测试
--last-failed 参数只运行上一次运行中失败的测试。做大规模改动导致多个测试失败时,这是最快的收敛手段。
容器内直接运行测试
避免每次测试都进出 Docker,可直接在 Docker 会话内运行:
tools/devtool -y shell --privileged
tools/test.sh integration_tests/functional/test_api.py
test.sh 会先在 /srv 建立 TMPDIR、为安全测试准备 cgroup v2 嵌套环境、按需把测试产物硬链接到 /srv/test_artifacts,随后在 tests 目录下执行 pytest。
用 pdb 单步调试失败用例
追加 --pdb,测试失败时即落入 pdb,可检查局部变量与调用栈,使用标准 Python REPL:
tools/devtool -y test -- -k 1024 integration_tests/performance/test_boottime.py::test_boottime --pdb
改用 ipython 的 ipdb
tools/devtool -y shell --privileged
export PYTEST_ADDOPTS=--pdbcls=IPython.terminal.debugger:TerminalPdb
tools/test.sh -k 1024 integration_tests/performance/test_boottime.py::test_boottime
devtool 已封装了一个更易输入的等价命令:
tools/devtool -y test_debug -k 1024 integration_tests/performance/test_boottime.py::test_boottime
test_debug 内部会带上 --no-build --no-kvm-check --no-build-dir-check --no-artifacts-check(见 tools/devtool),并设置 FC_TEST_SKIP_ARTIFACT_COPY=1。
交互式连接 guest 控制台
启用控制台的辅助函数必须在启动 Firecracker 进程之前调用:
uvm.help.enable_console()
uvm.spawn()
uvm.basic_config()
uvm.start()
...
随后若掉入 pdb,可打开一个连接到控制台(经 screen)的 tmux 标签页:
uvm.help.tmux_console()
复现间歇性(flaky)测试
循环运行目标测试,失败时自动落入 pdb:
while true; do
tools/devtool -y test -- integration_tests/functional/test_balloon.py::test_deflate_on_oom -k False --pdb
done
用 -n 并行运行测试
通过 pytest-xdist 并行。注意并非所有测试都能并行——build 与 performance 目录下的测试不应并行。默认串行执行;-n 控制并行度:单独 -n 会按 CPU 数起满 worker,可能过多。经验法则是用一半 CPU:8 核(超线程)笔记本可用 -n4;.metal 上 8 是个好数字,更多收益递减。--dist worksteal 使用 worksteal 调度:
tools/devtool -y test -- integration_tests/functional -n$(expr $(nproc) / 2) --dist worksteal
把 gdb 附加到运行中的 uvm
先让测试失败并落入 PDB,例如:
tools/devtool -y test_debug integration_tests/functional/test_api.py::test_api_happy_start --pdb
然后在 ipdb 提示符中启动 gdbserver:
ipdb> test_microvm.help.gdbserver()
它会输出如何运行 GDB 以附加到该 gdbserver 的指引。
用不同版本的 Firecracker 运行测试
集成测试通常会在初始化时现场编译 Firecracker,但也支持针对另一个版本(例如某个历史 release)运行:
./tools/devtool test -- --binary-dir ../v1.8.0
--binary-dir 指定的目录至少需要包含两个二进制:firecracker 与 jailer。--binary-dir 选项在 tests/conftest.py 中注册,并被 microvm_factory fixture 消费。同理,--custom-cpu-template 可让所有测试默认套用某个自定义 CPU template,除非被测试覆盖。
在 Docker 之外运行测试
已在 Ubuntu 22.04 与 AL2023 上验证。AL2 因 Python 太老(3.8)不可用:
# Fedora/AmazonLinux 请替换为 yum
sudo apt install python3-pip
sudo pip3 install pytest ipython requests psutil tenacity filelock "urllib3<2.0" requests_unixsocket aws_embedded_metrics pytest-json-report pytest-timeout
cd tests
sudo env /usr/local/bin/pytest integration_tests/functional/test_api.py
警告:这等于以 root 身份运行整套测试!这解释了上文 conftest 中 root 权限校验的原因——测试会创建系统级资源(网络命名空间、cgroup 等)。
Sandbox:IPython 交互式 REPL
tools/devtool -y sandbox
这会把你丢进一个 IPython REPL,可在其中与一台 microVM 交互:
uvm.help.print_log()
uvm.get_all_metrics()
uvm.ssh.run("ls")
snap = uvm.snapshot_full()
uvm.help.tmux_ssh()
devtool sandbox -- --help 可查看更多选项。若不使用 Docker,可参考 README 提供的原生运行路径(同样需要以 root 运行并安装 pytest ipython requests psutil tenacity filelock "urllib3<2.0" requests_unixsocket,再以 sudo env PYTHONPATH=tests ... ipython3 -i tools/sandbox.py -- --binary-dir ../repro/v1.4.1 方式启动)。
测试体系的设计目标与选型理由
为什么选 pytest
README 明确列出了选型理由,这些理由可以从配套 Python 代码中得到印证:
- Python 便于在云环境中工作;
- Python 内置沙箱(virtualenv)支持;
- pytest 具备出色的测试发现机制,支持简单、函数式的测试;
- pytest 拥有强大的 fixture 支持(tests/conftest.py 正是这套能力的集中体现)。
测试产物的内核矩阵
测试产物选择逻辑在 tests/framework/artifacts.py:guest 内核版本模式为 vmlinux-5.10.x、vmlinux-6.1.x、vmlinux-6.18.x,外加一个 vmlinux-5.10.x-no-acpi(MPTable 引导路径已弃用但仍需覆盖)。所有匹配内核都会进入 ALL_GUEST_KERNELS,被默认的 guest_kernel fixture 自动参数化。
术语对照
- Testrun:所有(或选定)集成测试的一次沙箱化运行。
- Test Session:一次 pytest 会话。每个 testrun 一个;sandbox 创建后会启动一次 Test Session。
- Test:一棵以
test_命名的函数,确保 Firecracker 的某个特性、功能参数或质量指标,失败时断言或抛异常。 - Fixture:返回一个便于添加 Test 的对象的函数(例如一台已启动的 Firecracker microVM),以
@pytest.fixture标记,定义于conftest.py或测试所在文件。 - Test Case:某个 Test 与其全部参数(含 fixtures)可能状态组成的笛卡尔积中的一个元素。
常见问题(FAQ)要点
- 已有 shell 测试脚本不想重写:尽量改写成 Python 测试函数;必要时用 shim Python 函数包装调用脚本,也可把它作为镜像资源放入 S3 bucket(会在
microvm.slot.path下可用),或在测试中拷入 guest 文件系统。 - 想加一些不打算提交进仓库的测试:testrun 前把你的测试目录放到
tests/下即可,pytest 会自动发现整棵测试树。 - 想用自己的 fixture 且不提交:在测试目录下放一个
conftest.py定义 fixture,pytest 会让它们对所有测试可见。 - 想用更多/其他 microVM 测试镜像但不想进公共 S3 bucket:把自定义镜像放到源码树
build/artifacts子目录,该目录会被 bind-mount 进容器并作为本地镜像缓存。 - 如何获取实时 logger 输出:修改 tests/pytest.ini 中的 logger 设置。
- 如何加速集成测试执行:按 "Running" 一节的两种方式收窄选择范围——用
-k substring只跑名字含某子串的子集,或只跑某文件/目录内的测试。
已知的测试系统 TODO
README 列出的 TODO 与对应源码文件中的注释一致:
功能方面:用 Firecracker Open API spec 生成 Microvm API 资源 URL;用事件驱动方式监听 microvm socket 文件创建以消除 while-spin;自测(测试测试系统本身的测试)。
实现方面:调研 pytest-ordering 以保证测试顺序;建立横跨测试运行器与 pytest 的分层 say 日志系统;按测试函数按需安装依赖;为 tests/* Python 模块补齐类型标注(当前类型提示使用稀疏)。
这些条目意味着测试基础设施本身仍在演进,阅读源码时若遇到相应注释,即可对照理解。
结语
Firecracker 的集成测试体系是"契约守护"思想的落地:Python 层通过 HTTP API 做黑盒验证,Rust 层直接驱动 VMM 程序化接口,Sanitizer 与 A/B 测试则分别覆盖内存安全与"无基准"的性能/安全回归。掌握 tools/devtool 的运行范式、FC_TEST_* 环境变量、conftest.py 中两层次的 fixture 体系以及 tools/ab_test.py 的 A/B 编排模型,就能在本地高效复现 CI 问题、为仓库贡献高质量的测试用例,并在不触碰源码的情况下完成跨 commit、跨环境的行为对比。对测试系统本身的深入理解,反过来也能帮助你更准确地解读 Firecracker 在安全性与性能上的真实承诺。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00