DEBUG.WIKI
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

pytest 先失败再卡在 teardown,faulthandler 超时为何没触发

9.1.1 普通失败也会取消 faulthandler 定时器,后续清理不再自动计时。以独立 CI 作业上限控制挂起,诊断与外部取消分别验收。

测试先断言失败,随后 fixture teardown 卡住,配置好的 faulthandler_timeout 却没有输出线程栈;即使打开 faulthandler_exit_on_timeout,进程也未按预期结束。pytest 9.1.1 中,应检查第一次普通失败是否已经取消了定时器。

该版在每个测试项的运行协议开始时安排一次 faulthandler 定时调用。普通失败也会进入 pytest_exception_interact,相应钩子取消定时器;后续 teardown 或失败子测试之后的测试体,不会因此自动重新计时。钩子名字带有 interact,不代表只有开启 --pdb 才执行。

外部作业截止时间不能只靠进程内定时器代替

CI 需要保证挂起任务最终结束时,应保留运行器之外的作业时间上限。例如已有 GitHub Actions 测试 job 可以加入下面的配置片段:

jobs:
  tests:
    timeout-minutes: 10

这是合并到现有 jobs.tests 的片段,保留原来的 runs-on、安装步骤和测试命令,不是单独可执行的完整工作流。10 分钟只是示例,应按正常作业耗时与允许上限设置;它限制的是整个 job,包括依赖安装,而不是每个 pytest 用例。

外部取消是防止任务无限占用的措施,不保证得到 Python 线程栈,也不保证 fixture 完成清理。外部服务、临时数据库与其他资源仍需按既有作业清理流程处理,不能把“job 已取消”当成所有副作用已撤销。

保留诊断配置,分别验证失败前后的阶段

faulthandler_timeout 仍有诊断价值,只是这条取消路径留下了覆盖缺口。不要把值改成零来消除等待,也不要把 pytest 内部钩子整段复制进 conftest 长期覆盖。仅安装另一个超时插件,也要先确认该正式版本的异常钩子行为,不能假设所有定时器相互独立且不会同样被取消。

原生 TOML 超时选项的类型错误是在配置读取阶段。本题已经启动并失败过一个测试,继续给秒数增删引号,不会重新安排已经取消的定时器。

排查时保留 pytest 版本、失败发生阶段、是否进入调试器与最终耗时。在隔离的 CI 作业复查正常通过后慢 teardown、断言失败后慢 teardown,以及 setup 出错后清理的不同路径;不要在正式业务进程制造死锁。作业截止时间应按运行器记录确认生效,与 faulthandler 是否输出分别记录。

恢复原作业限制前,先确认失败后的清理流程能够按项目要求结束,并保留原配置便于回退。pytest 9.1.1 的取消钩子仍有上述行为,不能只检查正常通过的测试能在阈值后打印栈,就认定失败后的阶段也受到覆盖。