> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 测试

> BlockX 的测试分层、每一层的命令与前置条件、测试替身、CI 与性能测试入口

BlockX 把测试按"保护哪一层真相"分开：core 单测保护状态机，adapter 测试保护协议映射，process E2E 保护单个进程的对外 contract，contract / system E2E 保护组件之间的边界，perf 单独保护吞吐与时延。这一页只讲怎么跑、在哪、要什么；环境搭建见 [本地开发环境](/development/getting-started)。

## 测试分层总览

除 `e2e/perf` 外，所有层都被 `go test ./...` 覆盖；需要 Python executor 的层在 venv 缺失时会 `t.Skip` 而不是失败（`internal/testutil/python.go` 的 `RequirePythonModules`）。

| 层                             | 位置                                                                                                                  | 命令                                                                                      | 耗时量级                    | 前置条件                                                                                                                | `-short`                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Go 单元测试（core / adapter / app） | `internal/**/*_test.go`（约 190 个文件）、`api/**/*_test.go`                                                               | `go test ./internal/... ./api/...`                                                      | 秒到分钟                    | 无外部依赖                                                                                                               | 只有 `internal/sdk/noderpc`、`internal/obs` 各一处检查                                |
| Worker process E2E            | `cmd/worker/worker_process_*_test.go`                                                                               | `go test -v -timeout 300s ./cmd/worker/...`                                             | CI 拆 8 个 shard，每个测试 90s | `uv sync --project python`；要求能 `import greenlet, blockx_sdk, blockx_executor, blockdb, blockx, leafage, chaintable` | 只跳过 `TestWorkerProcess_ConcurrentWorkers_And_Executors` 与 task\_finished 场景归档 |
| Bundle worker 启动冒烟            | `cmd/bundle_worker/bundle_worker_process_test.go`                                                                   | `go test -v -timeout 120s ./cmd/bundle_worker/...`                                      | 分钟内                     | 同上                                                                                                                  | 不跳过                                                                           |
| Coordinator process           | `cmd/coordinator/coordinator_process_lifecycle_test.go`、`cmd/bundle_coordinator/bundle_coordinator_process_test.go` | `go test ./cmd/coordinator/... ./cmd/bundle_coordinator/...`                            | 秒级                      | 内嵌 etcd，不需要 Python                                                                                                  | 不跳过                                                                           |
| Sync-invoker E2E              | `cmd/syncinvoker/syncinvoker_process_test.go`（23 个 `TestSyncInvoker_E2E_*`）、`syncinvoker_perf_test.go`              | `go test -v -timeout 300s ./cmd/syncinvoker/...`；`make test` 逐文件跑，每文件 `3m`              | 分钟级                     | venv（`greenlet, blockx_sdk, blockx_executor`）                                                                       | 不跳过；perf 用例由 `SYNC_PERF_N` 控制规模（默认 200）                                       |
| Contract E2E                  | `e2e/contract/coordinator-etcd/`、`e2e/contract/coordinator-worker/`                                                 | `go test -v -timeout 300s ./e2e/contract/...`                                           | 分钟级                     | venv + 内嵌 etcd；CI 先 `go build ./cmd/worker ./cmd/coordinator` 暖缓存                                                   | 不跳过                                                                           |
| System E2E                    | `e2e/system/{smoke,routing,recovery,logical_type}/`                                                                 | `go test -v -timeout 600s ./e2e/system/...`；快入口 `go test ./e2e/system/smoke/...`        | 分钟级                     | venv + 内嵌 etcd；`logical_type` 可用 `TEST_BLOCKDB_*_ADDR` 切到真实 BlockDB                                                 | 不跳过                                                                           |
| Perf                          | `e2e/perf/`、`cmd/perf`（打线上环境的 CLI）                                                                                  | `./test.sh` 或 `BLOCKX_RUN_PERF=1 go test -v -timeout 600s ./e2e/perf/...`               | 十分钟级                    | venv、`sudo`（cgroup）；见下文                                                                                             | `TestMain` 在 `-short` 下整包跳过，`BLOCKX_RUN_PERF_SHORT=1` 可覆盖                     |
| Python 单测                     | `python/tests/test_*.py`（43 个文件，无子目录）                                                                               | `PYTHONPATH=python uv run --project python python -m unittest discover -s python/tests` | 秒到分钟                    | `uv sync --project python`                                                                                          | 不适用                                                                           |

几点补充：

* `make test` 给所有包加了 `-short`，但除 perf 外没有一层因此被跳过，所以 `make test` 就是全量 E2E。
* `cmd/syncinvoker` 被 `Makefile` 从 `FAST_TEST_PACKAGES` 中剔除后逐文件跑：每个测试都起独立进程 + Python executor，perf 用例又不受 `-short` 保护，整包一起跑会超出预算；逐文件跑让 `SYNCINVOKER_TEST_TIMEOUT` 成为每个文件的预算，并分别产出覆盖率再合并。
* 单个模块的 `-run` 过滤示例：`go test ./internal/worker/core/ -run TestWorkerCore_RequestTaskSlot`。
* Python 单个模块：`PYTHONPATH=python uv run --project python python -m unittest python.tests.test_executor_protocol_contract`。

<Tabs>
  <Tab title="日常迭代">
    ```bash theme={null}
    go fmt ./... && go vet ./...
    go test ./internal/... ./api/...
    PYTHONPATH=python uv run --project python python -m unittest discover -s python/tests
    ```
  </Tab>

  <Tab title="提交前">
    ```bash theme={null}
    uv sync --project python
    make test                     # fmt + vet + 全量（含 E2E，syncinvoker 逐文件）
    ```
  </Tab>

  <Tab title="只跑某一层 E2E">
    ```bash theme={null}
    go test -v -timeout 300s ./cmd/worker/... -run TestWorkerProcess_RequestSlotAndSubmit
    go test -v -timeout 300s ./e2e/contract/coordinator-worker/...
    go test -v -timeout 600s ./e2e/system/smoke/...
    ```
  </Tab>
</Tabs>

## 测试组织约定

三份文件定义了怎么放、怎么命名：

* blockx 仓库 `docs/specs/worker-test-organization.md`：适用于 `internal/worker/**` 与 `cmd/worker/`、`cmd/bundle_worker/`。三个层级：`core unit`（只测 Sans-IO 核心，禁止引入 HTTP、进程或 Python）、`adapter integration`（handler / subscriber / orchestrator / executor pool，允许 fake 与 stub）、`process e2e`（真实 `cmd/worker` 进程，只放在 `cmd/<entrypoint>/`）。文件名表达实现边界：`worker_test.go`、`dispatcher_retry_test.go`、`handler_protocol_test.go`、`worker_process_<slice>_test.go`；禁止 `misc_test.go`、`e2e_test.go` 这类弱语义名。顶层函数 `Test<Boundary>_<Theme>`，`t.Run` 名必须写"场景 + 预期结果"。拆分阈值：一个文件超过 3 个逻辑块、一个顶层测试超过 8 个 `t.Run`、一个 process E2E 文件超过 6 个独立 contract。
* blockx 仓库 `docs/specs/python-test-organization.md`：`python/tests/` 一个文件一个实现边界，`test_<boundary>.py`；`TestCase` 名 `<Boundary><Theme>Test`；方法名"场景 + 预期结果"，例如 `test_deadline_after_resume_prevents_followup_waiting`。
* blockx 仓库 `docs/specs/system-e2e-testing.md`：contract 文件叫 `contract_<behavior>_test.go`、函数 `TestContractE2E_<Boundary>_<Behavior>`；system 文件叫 `system_<behavior>_test.go`、函数 `TestSystemE2E_<Behavior>`；一个 system 文件只放一个场景族。拿不准放哪层时默认放 contract 层。

`AGENTS.md` 的测试指南补充四条：Go 测试与实现放同目录的 `*_test.go`；状态机、协议映射、core/adapter 边界优先用表驱动；新行为要同时覆盖 happy path 与 spec 里的失败 / 超时路径；改了协议或生命周期语义就在同一个变更里更新对应 spec。

<Note>
  不同层断言不同真相：core 单测断状态迁移、幂等、命令输出、TTL 与结果收敛；adapter 测试断协议映射与错误翻译；process E2E 只断外部可观察行为（gRPC 返回、终态、日志字段）。不要在 E2E 里穷举 `DispatcherCore` 的 retry 矩阵，也不要用进程测试替代 handler 的错误映射断言。
</Note>

## 测试工具与替身

| 位置                                             | 内容                                                                                                                                                                                                                                                                                                                              |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `internal/testutil/etcd.go`                    | `StartEmbeddedEtcd` / `StartEmbeddedEtcdAt`：用 `go.etcd.io/etcd/server/v3/embed` 在测试进程内起 etcd，`t.Cleanup` 回收；`WaitForWorkerHeartbeat`、`WaitForWorkerHeartbeatGone` 观察注册                                                                                                                                                          |
| `internal/testutil/worker.go`、`coordinator.go` | `StartWorker` / `StartWorkerAt`、`StartCoordinator` / `StartBundleCoordinator`：编译并启动真实进程，注入最小环境变量，等 readiness。`BlockWorkerBlockDBEnv` 补 `BLOCKDB_BATCH_WRITE_ADDR` 与 `STUB_BLOCKDB_DELAY_MS=0`                                                                                                                                   |
| `internal/testutil/process.go`                 | `Process`：停止、日志、`RSSKB` / `VmPeakKB`、`KillOneChildPython`（故障注入）                                                                                                                                                                                                                                                                 |
| `internal/testutil/paths.go`                   | `BuildBinary`（每个测试进程只编译一次并缓存）、`FreeAddr`、`RunTestMain`（跑完清理二进制）、`pythonBin()` 探测 venv                                                                                                                                                                                                                                           |
| `internal/testutil/python.go`                  | `RequirePythonModules`：探测失败 `t.Skip`，结果按解释器缓存                                                                                                                                                                                                                                                                                   |
| `internal/testutil/rpc.go`、`rpc_types.go`      | `RequestTaskSlot`、`SubmitTask`、`PollUntilTerminal`、`GRPCCoordClient` / `GRPCWorkerClient`，以及 gRPC code 到业务错误码的反查                                                                                                                                                                                                                |
| `internal/testutil/blockdb.go`                 | `StartMockFunctionBlockDB`：进程内 gRPC 假 BlockDB，只实现 `GetRow` / `BatchGetRows` / `FilterRows` / `Scan`，用于 function code 读取                                                                                                                                                                                                         |
| `internal/testutil/sandbox_e2e.go`             | `RequireExecutorSandboxE2E`：containerd 沙箱 E2E 的前置检查（`ctr`、镜像、snapshotter），不满足即跳过。相关测试带 `//go:build sandbox_e2e`，默认不编译                                                                                                                                                                                                           |
| `internal/worker/devstub/`                     | dev / e2e 用的可插拔 stub：`StubBackendAdapter`、`DelayedBackendAdapter`（IO 后端）、`StaticCallBuilder` / `PayloadCallBuilder`（Builder）、`LogWriterPlugin` / `TestWriterPlugin`（Plugin）、`MemoryFunctionCodeStore`（function code）、`NoopEpochResolver` / `FailingEpochResolver`。只被 `cmd/worker` 与 `cmd/syncinvoker` 的 profile 注册，bundle 入口不注册 |
| `cmd/blockdb_grpc_stub/`                       | 独立的 BlockDB gRPC stub 进程（`-listen`、`-read-delay`、`-probe-address` 等 flag），benchmark 用；不含 Scan / Subscribe                                                                                                                                                                                                                       |
| `internal/sdk/localtestservice/`               | 示范能力后端：`Service` + `Adapter`，worker 默认注册；`LOCAL_TEST_SERVICE_DISABLED=true` 关闭。`cmd/worker/worker_process_localtestservice_test.go` 覆盖它                                                                                                                                                                                         |
| `examples/local_test_service/`                 | 用 blockx-py 打本地 worker 的示例（`local_proxy.py` + `submit.py`），不是自动化测试                                                                                                                                                                                                                                                              |

testutil 没有 fake clock。core 的方法显式接收 `now`（例如 `WorkerCore.HandleRequestTaskSlot(taskID string, ttlMs int64, now int64)`），core 单测直接传时间值即可。

## CI

`.github/workflows/test.yml` 在 PR 到 `dev`、push 到 `dev` 与手动触发时运行；除 `Deploy Script Tests` 外的 job 都通过 GitHub App token 配置 `github.com/Chaintable/*` 私有模块访问：

| Job                              | 内容                                                                                                                                                                                   |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Deploy Script Tests`            | `bash deploy/tests/*.sh`：worker / syncinvoker 的 entrypoint、profile、env resolver、reconcile、health、deploy bundle 脚本测试                                                                  |
| `Go Tests`                       | `go test ./internal/... ./api/... ./cmd/...`。这个 job 不装 Python 依赖，`cmd/*` 下依赖 executor 的 process E2E 在 `RequirePythonModules` 探测失败时跳过；coordinator process 测试与各 `profile_test.go` 正常执行 |
| `Python Tests`                   | `uv sync` 后 `uv run python -m unittest discover -s tests`（`working-directory: python`）                                                                                               |
| `Worker Process E2E (1/8 … 8/8)` | `bash .github/scripts/run-go-test-shard.sh ./cmd/worker <index> 8 90s`：编译测试二进制，按测试名 `cksum` 分 8 片，逐个测试跑，每个 90s                                                                       |
| `Bundle Worker Process E2E`      | `go test -v -timeout 120s ./cmd/bundle_worker/...`                                                                                                                                   |
| `Contract E2E`                   | `go build ./cmd/worker ./cmd/coordinator && go test -v -timeout 300s ./e2e/contract/...`                                                                                             |
| `System E2E`                     | `go test -v -timeout 600s ./e2e/system/...`                                                                                                                                          |
| `Perf E2E`                       | `go test -v -short -timeout 900s ./e2e/perf/...`，带 `PERF_N=8 PERF_C=2 PERF_EXECUTOR_COUNT=2 PERF_SLO_MULTIPLIER=5`                                                                   |

`.github/workflows/build.yml` 只在推 tag 时构建并推送镜像，不跑测试。

## 性能测试

`e2e/perf` 每个用例自起 etcd + coordinator + worker，用 `PERF_*` 环境变量控制规模，`checkSLO` 用 `PERF_SLO_MULTIPLIER` 放大后的阈值断言时延，阈值来源见 `docs/specs/perf-methodology.md`。

整包由 `e2e/perf/short_test.go` 的 `TestMain` 门控：`BLOCKX_RUN_PERF` 不为 `1` 时直接返回，一个用例都不跑。

```bash theme={null}
# 标准入口：在 cgroup（默认 CPUQuota=400%、MemoryMax=8G、禁 swap）内跑，需要 sudo
./test.sh
./test.sh -run TestPerf_SchedulerConcurrency
PERF_CPU=200% PERF_MEM=4G ./test.sh

# 不限资源直接跑（结果偏乐观）；必须显式打开门控
BLOCKX_RUN_PERF=1 go test -v -timeout 600s ./e2e/perf/...
BLOCKX_RUN_PERF=1 PERF_N=10 PERF_IO_READS=3 go test -v -timeout 300s -run TestPerf_CacheLocality ./e2e/perf/...
```

* 主要变量：`PERF_N`（每场景 task 数，默认 20）、`PERF_C`（并发，默认 4）、`PERF_EXECUTOR_COUNT`（默认 4）、`PERF_IO_READS`（默认 5）、`PERF_SLO_MULTIPLIER`（默认 1.0，CI 用 5）。`test.sh` 自身读 `PERF_CPU`、`PERF_MEM`、`PERF_TIMEOUT`，并透传 `WORKER_CPU_PROFILE`、`WORKER_TRACE`、`COORDINATOR_CPU_PROFILE`、`COORDINATOR_TRACE`。
* 文件按 `docs/specs/perf-methodology.md` §3 的 Stage 命名：`s0_framework_*`、`s1_builder_*`、`s2_call_count_*`、`s3_cpu_*`、`s4_io_*`、`s5_plugin_*`；`phase_*`、`m3_stream_build_test.go`、`calibration_test.go` 是规范之前的遗留文件。`helpers_test.go` 放 `runScenario` / `startPerfInfra*` / `checkSLO` 等脚手架。
* 产物落 `e2e/perf/_artifacts/`（已 gitignore）：`<TestName>_<subtest>/spans.jsonl`（`with_trace` 子测试的 OTel span，开关 `BLOCKX_OTEL_SPAN_FILE` + `BLOCKX_OTEL_UDS_EVENTS=1`）、`<TestName>/*.pb.gz`（pprof）、`pyspy/`。
* 离线分析 span：`python3 analyze_spans.py e2e/perf/_artifacts/<TestName>_with_trace/spans.jsonl`。脚本在仓库根，只接受一个位置参数，默认 `/tmp/blockx_spans.jsonl`。
* 报告：`docs/specs/perf-test-report.md`（v1 主报告）、`perf-test-report-appendix.md`（v1 附录）、`perf-test-report-v2.md`（当前唯一基线，cgroup 12 vCPU / 32G）、`perf-capability-backend-report.md`（能力后端框架税）。复现步骤在 `docs/specs/perf-test-how-to-v2.md`，方法论在 `docs/specs/perf-methodology.md`。
* `cmd/perf` 是打**已部署**环境的负载 CLI（`-coordinator`、`-n`、`-c`、`-mode`），不属于 `go test`。`cmd/bundle-loadgen` 是 bundle 集群的负载生成器，自带纯单测。

## 相关文档

* [本地开发环境](/development/getting-started)：venv、CGO、最小 worker 启动。
* [贡献流程](/development/contributing)：PR 里要写验证命令。
* [Worker](/components/worker)、[Coordinator](/components/coordinator)、[Sync Invoker](/components/sync-invoker)、[Bundle 集群](/components/bundle)：各组件页的"测试"小节指向对应文件。
* blockx 仓库 `docs/specs/worker-test-organization.md`、`docs/specs/python-test-organization.md`、`docs/specs/system-e2e-testing.md`：组织规范。
* blockx 仓库 `docs/specs/perf-methodology.md`、`docs/specs/perf-test-how-to-v2.md`、`e2e/perf/README.md`：性能测试方法、复现与用例索引。
* blockx 仓库 `AGENTS.md`：命令与测试指南的权威来源。
