python在永不结束的进程上运行覆盖率
我有一个多进程的web服务器,它的进程永远不会结束,我想在一个实时环境中检查整个项目的代码覆盖率(不仅仅是测试) 问题是,由于进程永远不会结束,我没有一个好位置来设置python在永不结束的进程上运行覆盖率,python,multithreading,python-multiprocessing,coverage.py,Python,Multithreading,Python Multiprocessing,Coverage.py,我有一个多进程的web服务器,它的进程永远不会结束,我想在一个实时环境中检查整个项目的代码覆盖率(不仅仅是测试) 问题是,由于进程永远不会结束,我没有一个好位置来设置cov.start()cov.stop()cov.save()挂钩 因此,我考虑在无限循环中生成一个线程,该线程将保存并合并覆盖率数据,然后在一段时间内休眠,但是这种方法不起作用,覆盖率报告似乎是空的,除了休眠行 我很乐意收到关于如何覆盖我的代码的任何想法, 或者任何关于为什么我的想法行不通的建议。以下是我的代码片段: import
cov.start()cov.stop()cov.save()
挂钩
因此,我考虑在无限循环中生成一个线程,该线程将保存并合并覆盖率数据,然后在一段时间内休眠,但是这种方法不起作用,覆盖率报告似乎是空的,除了休眠行
我很乐意收到关于如何覆盖我的代码的任何想法,
或者任何关于为什么我的想法行不通的建议。以下是我的代码片段:
import coverage
cov = coverage.Coverage()
import time
import threading
import os
class CoverageThread(threading.Thread):
_kill_now = False
_sleep_time = 2
@classmethod
def exit_gracefully(cls):
cls._kill_now = True
def sleep_some_time(self):
time.sleep(CoverageThread._sleep_time)
def run(self):
while True:
cov.start()
self.sleep_some_time()
cov.stop()
if os.path.exists('.coverage'):
cov.combine()
cov.save()
if self._kill_now:
break
cov.stop()
if os.path.exists('.coverage'):
cov.combine()
cov.save()
cov.html_report(directory="coverage_report_data.html")
print "End of the program. I was killed gracefully :)"
既然您愿意为测试以不同的方式运行代码,为什么不添加一种结束测试过程的方法呢?这似乎比尝试破解覆盖范围要简单。显然,使用多个
线程无法很好地控制覆盖范围。
一旦启动了不同的线程,停止覆盖
对象将停止所有覆盖,而启动
将仅在“启动”线程中重新启动它。
因此,除了CoverageThread
之外,对于所有Thread
线程,您的代码基本上会在2秒钟后停止覆盖
我使用了一点API,可以在不停止Coverage
对象的情况下访问测量值。
因此,您可以启动一个线程,使用API定期保存覆盖率数据。
第一个实现是这样的
import threading
from time import sleep
from coverage import Coverage
from coverage.data import CoverageData, CoverageDataFiles
from coverage.files import abs_file
cov = Coverage(config_file=True)
cov.start()
def get_data_dict(d):
"""Return a dict like d, but with keys modified by `abs_file` and
remove the copied elements from d.
"""
res = {}
keys = list(d.keys())
for k in keys:
a = {}
lines = list(d[k].keys())
for l in lines:
v = d[k].pop(l)
a[l] = v
res[abs_file(k)] = a
return res
class CoverageLoggerThread(threading.Thread):
_kill_now = False
_delay = 2
def __init__(self, main=True):
self.main = main
self._data = CoverageData()
self._fname = cov.config.data_file
self._suffix = None
self._data_files = CoverageDataFiles(basename=self._fname,
warn=cov._warn)
self._pid = os.getpid()
super(CoverageLoggerThread, self).__init__()
def shutdown(self):
self._kill_now = True
def combine(self):
aliases = None
if cov.config.paths:
from coverage.aliases import PathAliases
aliases = PathAliases()
for paths in self.config.paths.values():
result = paths[0]
for pattern in paths[1:]:
aliases.add(pattern, result)
self._data_files.combine_parallel_data(self._data, aliases=aliases)
def export(self, new=True):
cov_report = cov
if new:
cov_report = Coverage(config_file=True)
cov_report.load()
self.combine()
self._data_files.write(self._data)
cov_report.data.update(self._data)
cov_report.html_report(directory="coverage_report_data.html")
cov_report.report(show_missing=True)
def _collect_and_export(self):
new_data = get_data_dict(cov.collector.data)
if cov.collector.branch:
self._data.add_arcs(new_data)
else:
self._data.add_lines(new_data)
self._data.add_file_tracers(get_data_dict(cov.collector.file_tracers))
self._data_files.write(self._data, self._suffix)
if self.main:
self.export()
def run(self):
while True:
sleep(CoverageLoggerThread._delay)
if self._kill_now:
break
self._collect_and_export()
cov.stop()
if not self.main:
self._collect_and_export()
return
self.export(new=False)
print("End of the program. I was killed gracefully :)")
更稳定的版本可以在这里找到。
这段代码基本上是在不停止的情况下获取收集器收集的信息。
get\u data\u dict
函数在Coverage.collector
中获取字典并弹出可用数据。这应该足够安全,因此不会丢失任何测量值。
报告文件每延迟秒更新一次
但是,如果有多个进程正在运行,则需要额外努力以确保所有进程都运行CoverageLoggerThread
。这是patch\u多处理
功能,从覆盖范围
monkey patch…
代码在目录中。它基本上用自定义进程替换了原始进程,该进程在运行run
方法之前启动CoverageLoggerThread
,并在进程结束时加入线程。
脚本main.py
允许使用线程和进程启动不同的测试
此代码有2/3的缺点,您需要小心:
- 同时使用
combine
功能是个坏主意,因为它对.coverage.*
文件执行当前的读/写/删除访问。这意味着功能export
不是超级安全的。它应该是好的,因为数据被多次复制,但我会在生产中使用它之前做一些测试
- 导出数据后,数据将保留在内存中。因此,如果代码库庞大,它可能会消耗一些资源。可以转储所有数据并重新加载,但我假设如果您希望每2秒记录一次日志,则不希望每次都重新加载所有数据。如果延迟几分钟,我每次都会创建一个新的
\u数据
,使用coverage data.read\u file
重新加载此进程以前的覆盖状态
- 自定义进程将等待
\u delay
完成,因为我们在进程结束时加入了CoverageThreadLogger
,因此如果您有很多快速进程,您需要增加睡眠的粒度,以便能够更快地检测到进程的结束。它只需要一个自定义的睡眠循环,在\u kill\u now
时中断
让我知道这是否在某种程度上对您有所帮助,或者是否有可能改进此要点
编辑:
似乎您不需要对多处理模块进行猴子补丁就可以自动启动记录器。在python安装中使用.pth
可以使用环境变量在新进程上自动启动记录器:
# Content of coverage.pth in your site-package folder
import os
if "COVERAGE_LOGGER_START" in os.environ:
import atexit
from coverage_logger import CoverageLoggerThread
thread_cov = CoverageLoggerThread(main=False)
thread_cov.start()
def close_cov()
thread_cov.shutdown()
thread_cov.join()
atexit.register(close_cov)
然后,您可以通过以下两个程序直接使用coverage\u logger\u start=1 python main.y
启动覆盖率记录器
# start.py
import sys
import coverage
sys.cov = cov = coverage.coverage()
cov.start()
这个呢
# stop.py
import sys
sys.cov.stop()
sys.cov.save()
sys.cov.html_report()
另一种方法是使用跟踪程序,即使它只打印调用,它也会很有用。您到底想测量什么?“覆盖率”具体指测试。您是否正在尝试查看代码的哪些部分实际执行?是否要检查您的.py文件是否仍处于活动状态?是的,代码库非常庞大,我们希望查看哪些代码区域从未到达,如果这些区域存在,请将其删除,或检查其无法访问的原因。@Rizzit我知道这些文件处于活动状态,但我实际上想知道执行的是什么。Coverage使用解释器挂钩来通知代码中正在执行的每一行。这将显著降低性能,您愿意为此付出代价吗?因为数据应该以无缝的方式收集给最终用户,所以我们的想法是从公司的多个用户收集数据,并合并他们的结果,同时允许他们以正常的方式工作。此外,如果我将强制进程重新启动或停止,这将影响到将损害结果的行为。感谢您提出这一点。它在coverage.py代码库中使用了许多非公共接口。coverage.py可以提供什么样的公共API使其更受支持?还有,为什么要调用combine?为什么不将每个快照写入一个单独的文件,然后在以后合并它们呢?Coverage可以提出一个导出功能,将当前数据保存在一个文件中,并且不停止Coverage
对象。这是一个非常复杂的问题