Pytest 测试so库时遇到Python Fatal Error 继续执行测试,并生成allure报告
pytest遇到Segmentation后继续运行后续测试
使用pytest-xdist分布式运行, woker节点崩溃后xdist会重新启动新进程进行测试, 并向自己的Terminal 里报告fail。但是这条fail, Allure 报告抓取不到。
Allure报告展示crash时case结果为fail
pytest-xdist内置pytest_handle_crashitem钩子函数, 在conftest.py里实现这个钩子函数并进行处理。 因为crash之后后续的hook函数都不会被运行,所有需要在pytest_handle_crashitem钩子函数里主动调用,以生成allure数据
conftest.py
@pytest.hookimp(tryfirst=True)
def pytest_handlecrashitem(crashitem,report, sched):
allure_listener = report.node.config.pluginmanager.get_plugin("allure-listener") #获取allure_listener
allure_listener_hook = allure_listener.config.hook #获取allure_listener的hook
item = copy.copy(report)
gen_test_result(allure_listener, item) #生成allure报告,这样才会有数据写到allure的json文件里
allure_listener_hook.pytest_runtest_logfinish(nodeid=crashitem, location=report.location) #这个钩子函数会调到allure_listener.allue_logger.close_test(uuid). 最终把结果写入result.json
gen_test_result()主要用于生成测试报告,否则最终生成的reslut.json文件没有内容
AllureListener 位于site-packages/allure_pytest/listener.py
from allure_common.utils import md5,platform_label
from allure_common.model2 import Label, TestResult
from allure_common.types import LabelType
from allure_pytest.utils import allure_package
def gen_test_result(allure_listener, item):
uuid=allure_listener._cache.push(item.nodeid)
crashitmeList = item.nodeid.split("::")
name = crashitemList[-1]
test_result = TestResult(name=name, uuid=uuid, start=now(), stop=now())
allure_listener.allure_logger.schedule_test(uuid, test_result)
for i in range(len(crashitemList)-1):
crashitemList[i]=crashitemList[i].replace(os.sep, ".")
index = name.find("[")
if index !=-1:
crashitemList[-1] = crashitemList[-1][:index]
full_name = "."join(crashitemList[:-1])+"#"+crashitemList[-1]
test_result.fullName = full_name
test_result.historyId = md(item.nodeid)
test_result.testCaseId = md5(full_name)
test_result.labels.extend([Label(name=name, value=value) for name, value in allure_suite_labels(item)]) #生成suite
test_result.labels.append(Label(name=LabelType.HOST, value=allure_listener._host) )
test_result.labels.append(Label(name=LabelType.THREAD, value=allure_listener._thread) )
test_result.labels.append(Label(name=LabelType.FRAMEWORK, value="pytest") )
test_result.labels.append(Label(name=LabelType.LANGUAGE, value=platform_label()) )
test_result.labels.append(Label(name="package", value=allure_package(item)) )
test_result.status="failed"
from _pytest.code import getfslineno
path, lineno = getfslineno(item)
test_result.statusDetail=StatusDetails(message=info)
###以下时等待core file生成并把core dump attach到allure报告里
cmd = r"""ps aux | grep "\[python3\] <defunct>" | awk '{print $2}' """ #获取crash的进程id
result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
pid = result.stdout.decode().strip()
coreFile= f"core.{pid}" # core dump文件命名为core.pid
q=queue.Queue()
th = threading.Thread(target=waitQForfile, args=(q, coreFile))
th.start()
try:
q.get(timeout=60)
except queue.Empty:
logging.error("wait core dump failed")
else:
allure_listener.allure_logger.attach_file(uuid=uuid, source=coreFile, name="core dump") # attach core dump到allure报告
def allure_suite_labels(item): #重写了site-package/allure_pytest_utils.py#allure_suite_labels
head, possibly_clazz, tail = isslice(chain(item.nodeid.split('::'), [None],[None]), 3)
clazz = possibly_clazz if tail else None
file_name, path = islice(chain(reversed(head.raplit('/',1)), [None]),2)
module = file_name.split('.')[0]
package = path.replace('/', '.') if path else None
pairs = dict(zip([LabelType.PARENT_SUITE, LabelType.SUITE, LabelType.SUB_SUITE], [package,module, clazz]))
labels = dict() #这里因为获取不到item的label,所以直接赋值空字典
default_suite_labels = []
for label, value in pair.items():
if label not in labels.keys() and value:
default_suite_labels.append(label,value))
return default_suite_labels
def waitQForfile(q,file):
while True:
if os.path.exists(file):
q.put("done")
break
else:
tiem.sleep(1)