DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →

Trio 包装 MockClock 后 sleep 挂住,自动跳时依赖同一对象

自定义 Clock 转发 MockClock 方法,可能保留读数却丢失自动跳时关联。让 Trio 运行器直接使用同一个 MockClock,区分虚拟时间、日期基准和其他未阻塞任务。

测试把 MockClock(autojump_threshold=0) 包在自定义 Clock 里,三个方法都转发给内部对象,await trio.sleep(2) 却一直不返回;直接把内部 MockClock 传给 trio.run() 又正常。这个场景要检查传入运行器的对象身份,不能只核对方法签名。

Trio 0.32.0 有这类完整记录,正式 0.34.0 的 MockClock 仍通过运行器内部状态协同自动跳时。实现了 trio.abc.Clock,并不意味着包装一个 MockClock 就自动继承了它的全部跳时行为。

包装转发保留了读数,丢掉了自动跳时关联

默认 rate=0 的 MockClock 不随真实时间前进,主要依靠手动 jump() 或自动跳时推进。它初始化自动跳时规则时,会检查运行器正在使用的 clock 是否就是自己。

若 trio.run(clock=wrapper) 接收的是外层对象,这个身份判断不成立。内部 MockClock 的 deadline_to_sleep_time() 在正向期限、零速率条件下仍会返回一个很大的等待值,自动跳时的运行器设置却没有同步,原本应立即完成的测试就可能长时间挂住。

这不表示第三方无法实现 Clock 抽象接口。限制在于 MockClock 这一具体实现额外使用了内部协作机制;普通的读数转发没有转发这份关系。

让运行器直接持有同一个 MockClock

测试确实需要自动跳时,且应用可以接收这个时钟对象时,优先把同一实例直接交给 Trio 和应用的依赖注入入口。下面的片段展示运行器侧的完整用法:

import trio
from trio.testing import MockClock

clock = MockClock(autojump_threshold=0)

async def main():
    before = clock.current_time()
    await trio.sleep(2)
    assert clock.current_time() >= before + 2

trio.run(main, clock=clock)

如果应用也接收 clock,传入这里同一个对象,而不是另建一个 MockClock 或再次包装后交给运行器。应用还需要额外的日期换算接口时,可让应用适配层持有它,但运行器仍直接使用底层 MockClock;适配层不要冒充运行器使用的 clock。

MockClock 提供的是测试中的虚拟时间,不能把这些读数直接当成生产 Unix 时间戳。涉及绝对日期、过期时间与真实外部服务的测试,仍需要明确日期基准和换算规则。单纯设置 rate=1 也不是对自动跳时行为的等价替换。

不要复制 Trio 私有 MockClock 类、改写 GLOBAL_RUN_CONTEXT,或给运行器私有属性赋值来让一次测试通过。这些做法依赖内部类型和调度约定,升级后可能变成 TrioInternalError。

还有任务未阻塞时,自动跳时本来就不会发生

即便对象身份正确,自动跳时也要等所有相关任务阻塞,并遵守 autojump_threshold 与 wait_all_tasks_blocked 的优先关系。后台忙循环、真实外部 I/O 和另一组等待任务可能改变时机,不应把所有挂住都归给包装器。

在隔离测试中分别核对直接 MockClock、应用持有同一实例、含并发任务及实际 I/O 的路径。设置测试执行器的外部超时,避免一次挂住占满 CI;超时是防护,不是修复时钟。验收应确认期望的超时与过期分支真正触发,不能只把 sleep 删除后看测试变绿。