bashcov无法统计Python间接调用的嵌套bash子程序的覆盖率,如何解决
Python的subprocess模块默认行为是关闭除标准流(FD 0/1/2)外的所有继承文件描述符。bashcov通过环境变量BASH_XTRACEFD指定的追踪管道FD(示例中为8号)被Python关闭后,下游启动的bash进程读取到BASH_XTRACEFD=8但无法找到对应有效的文件描述符,才会出现BASH_XTRACEFD: 8: invalid value for trace file descriptor报错,最终xtrace数据无法写入bashcov的收集管道,覆盖率统计缺失sub.sh的相关数据。
方案1:使用pass_fds参数精准保留所需FD(推荐,Python 3.2+支持)
subprocess模块提供的pass_fds参数可指定当前进程中需要传递给子进程的文件描述符,恰好解决默认关闭非标准FD的问题。
bashcov启动时会将trace FD编号写入环境变量BASH_XTRACEFD,只需在Python代码中读取该值并传入pass_fds即可。该写法无需硬编码FD编号,可适配不同环境下bashcov自动分配的不同trace FD,通用性更强。
修改后的sub.py示例:
import os import subprocess print("running subprograms") # 读取bashcov设置的追踪FD,若存在则加入保留列表 pass_fds = [] xtrace_fd_str = os.environ.get("BASH_XTRACEFD") if xtrace_fd_str: pass_fds.append(int(xtrace_fd_str)) # 调用sub.sh时传入pass_fds参数 subprocess.run(["./sub.sh"], pass_fds=pass_fds)
为什么传入shell=True无效?
shell=True会先启动一个中间bash进程执行目标命令,但Python对这个中间bash进程执行subprocess.run时,依然会默认关闭非标准FD,导致trace FD在中间bash层就已经失效,下游的sub.sh自然无法获取有效FD。
方案2:旧版Python兼容方案(Python <3.2,无pass_fds时使用)
如果使用Python 3.2之前的版本,可通过preexec_fn关闭close_fds并指定保留FD,但需注意该方案会传递所有FD给子进程,存在安全风险:
import os import subprocess def retain_xtrace_fd(): xtrace_fd = int(os.environ.get("BASH_XTRACEFD", -1)) if xtrace_fd > 0: # 将目标FD标记为可继承 os.set_inheritable(xtrace_fd, True) subprocess.run( ["./sub.sh"], preexec_fn=retain_xtrace_fd, close_fds=False )
修改完成后,可在sub.sh开头添加一行ls -l /proc/$$/fd/,执行bundle exec bashcov ./main.sh时查看输出:
- 若能看到和bash直接调用时一致的trace FD(示例中的8号)被保留在FD列表中,说明配置生效
- 此时生成的覆盖率报告将正常展示
sub.sh的覆盖率数据
注意:如果调用链路中还有更多中间层(例如Python下再启动其他语言进程),每一层调用下游bash进程时都需要传递对应的trace FD,否则仍会出现统计失效问题。
内容的提问来源于stack exchange,提问作者Alex028502

