Skip to content

[P1] 清理 sleep/poll 驱动测试:建立可等待事件与可注入时钟的确定性测试基建 #1550

Description

@DeliciousBuding

结论

仓库中多处并发、event bus、rate limit、WebSocket、adapter 与 lifecycle 测试依赖固定 time.Sleep 或无 deadline 的 polling loop 来等待异步行为。

典型示例:

  • hub-server/internal/service/eventbus_test.go
    • 多个测试在 Publish 后固定 Sleep(50ms)
    • for Running/Pending > 0 { Sleep(5ms) } 没有总 deadline。
  • 搜索还命中:
    • Hub middleware/ws/cache/bus/service tests;
    • Edge events/adapters/lifecycle/hub tests;
    • integration WS reconnect tests。

固定 sleep 既可能太短导致 CI 偶发失败,也可能过长拖慢测试;无 deadline polling 在缺陷出现时可能永久挂住。它们还掩盖了生产组件缺少 Wait/Drain/Clock seam 的设计问题。

风险

  1. 不同 CI 负载/平台下 flaky;
  2. race 模式更慢时概率性失败;
  3. 行为未发生时测试长时间挂死;
  4. 为降低 flake 不断增加 sleep,测试套件越来越慢;
  5. 无法精确证明 backoff、heartbeat、timeout、queue drain 的状态转换;
  6. 生产组件没有可观察 completion API,只能靠测试窥探内部计数器。

目标设计

建立仓库级测试等待原则:

事件完成

使用 channel、WaitGroup、barrier、callback probe 或组件提供的 WaitIdle(ctx)

select {
case got := <-handled:
    // assert
case <-time.After(testDeadline):
    t.Fatal("handler not called")
}

时间行为

  • 注入 clock/ticker/timer factory;
  • 使用 fake/manual clock 推进时间;
  • heartbeat/backoff/TTL/rate-limit 不依赖真实秒级等待。

Polling

若必须 eventually:

  • 使用统一 helper;
  • 有明确 deadline、poll interval、失败时状态 dump;
  • 禁止裸无限循环。

生产 API

对异步组件增加必要且非 test-only hack 的 lifecycle/observability:

  • WaitIdle(ctx)
  • Drain(ctx)
  • Start(ctx) / Close()
  • injectable clock;
  • completion/result channel。

实施步骤

  1. 建立脚本盘点 _test.gotime.Sleeptime.After、裸 polling;
  2. 分类:
    • 合法的 deadline guard;
    • 真实时间 integration;
    • 可替换的同步等待;
    • 需要 clock seam;
  3. 先处理高频/高 flake 包:Hub bus/ws/middleware,Edge events/lifecycle/adapters;
  4. 增加 ratchet:存量清单只能下降,新 unit test 不得新增固定 sleep;
  5. integration 中保留真实时间时必须有原因、上限和稳定性证据。

必须测试/验收

  • 目标包 -count=50 稳定;
  • race lane -count=10
  • 测试总时长 before/after;
  • 人工删除/阻断目标事件时,测试在短 deadline 内明确失败而非挂住;
  • fake clock 能验证 1s/2s/4s backoff、heartbeat expiry、TTL;
  • verifier 自测:新增 time.Sleep fixture 会失败,合法 allowlist 可解释。

推荐命令:

go test ./hub-server/internal/bus/... -count=50
go test -race ./hub-server/internal/bus/... -count=10
go test ./edge-server/internal/events/... ./edge-server/internal/lifecycle/... -count=50

按实际 module 目录调整。

设计约束

完成条件

  • unit/contract tests 中无未经批准的固定 sleep;
  • 所有 polling 有 deadline;
  • 关键时间组件可注入 clock;
  • 高风险包 -count=50 和 race 重复运行稳定;
  • CI 有防新增 ratchet。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions