我有一个多处理的Web服务器,其流程永无止境,我想在实时环境中检查整个项目的代码覆盖率(不仅仅是测试)。
问题是,由于进程永远不会结束,我没有一个设置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 :)"
答案 0 :(得分:8)
显然,使用多个coverage
无法很好地控制Threads
。
一旦启动了不同的线程,停止Coverage
对象将停止所有覆盖,start
将仅在“启动”线程中重新启动它。
因此,除Thread
之外的所有CoverageThread
,您的代码基本上会在2秒后停止覆盖。
我玩了一些API,可以在不停止Coverage
对象的情况下访问测量。
因此,您可以使用API启动定期保存coverage数据的线程。
第一个实现就像在这个
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 :)")
在此GIST中可以找到更稳定的版本。
此代码基本上抓取收集器收集的信息而不停止它。
get_data_dict
函数获取Coverage.collector
中的字典并弹出可用数据。这应该足够安全,这样你就不会丢失任何测量值
报告文件每_delay
秒更新一次。
但是如果你有多个进程在运行,你需要额外的努力来确保所有进程都运行CoverageLoggerThread
。这是patch_multiprocessing
函数,猴子从coverage
猴子补丁修补...
代码位于GIST。它基本上用自定义进程替换原始进程,该进程在运行CoverageLoggerThread
方法之前启动run
并在进程结束时加入线程。
脚本main.py
允许使用线程和进程启动不同的测试。
此代码存在2/3的缺点,需要注意:
同时使用combine
函数是一个坏主意,因为它对.coverage.*
文件执行comcurrent读/写/删除访问。这意味着函数export
不是超级安全的。它应该没问题,因为数据被多次复制,但我会在生产中使用之前进行一些测试。
导出数据后,它将保留在内存中。因此,如果代码库很大,它可能会吃掉一些资源。可以转储所有数据并重新加载它但我假设如果要每2秒记录一次,则不希望每次都重新加载所有数据。如果您在几分钟内延迟,我会每次创建一个新的_data
,使用CoverageData.read_file
重新加载此过程的覆盖范围的先前状态。
自定义流程将在结束前等待_delay
,因为我们在流程结束时加入CoverageThreadLogger
,因此如果您有大量快速流程,则需要增加粒度睡眠能够更快地检测到过程的结束。它只需要一个在_kill_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_LOGGER_START=1 python main.y
答案 1 :(得分:3)
由于您愿意为测试运行不同的代码,为什么不添加一种方法来结束测试过程?这似乎比试图破解报道更简单。
答案 2 :(得分:1)