add new pytest_handlecrashitem hook
allows handling and rescheduling crash tests PR #651 Co-authored-by: xoviat <xoviat@users.noreply.github.com> Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
This commit is contained in:
1
changelog/650.feature.rst
Normal file
1
changelog/650.feature.rst
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Added new ``pytest_handlecrashitem`` hook to allow handling and rescheduling crashed items.
|
||||||
@@ -343,6 +343,12 @@ class DSession:
|
|||||||
nodeid, (fspath, None, fspath), (), "failed", msg, "???"
|
nodeid, (fspath, None, fspath), (), "failed", msg, "???"
|
||||||
)
|
)
|
||||||
rep.node = worker
|
rep.node = worker
|
||||||
|
|
||||||
|
self.config.hook.pytest_handlecrashitem(
|
||||||
|
crashitem=nodeid,
|
||||||
|
report=rep,
|
||||||
|
sched=self.sched,
|
||||||
|
)
|
||||||
self.config.hook.pytest_runtest_logreport(report=rep)
|
self.config.hook.pytest_runtest_logreport(report=rep)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,3 +64,20 @@ def pytest_xdist_auto_num_workers(config):
|
|||||||
|
|
||||||
.. versionadded:: 2.1
|
.. versionadded:: 2.1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.firstresult
|
||||||
|
def pytest_handlecrashitem(crashitem, report, sched):
|
||||||
|
"""
|
||||||
|
Handle a crashitem, modifying the report if necessary.
|
||||||
|
|
||||||
|
The scheduler is provided as a parameter to reschedule the test if desired with
|
||||||
|
`sched.mark_test_pending`.
|
||||||
|
|
||||||
|
def pytest_handlecrashitem(crashitem, report, sched):
|
||||||
|
if should_rerun(crashitem):
|
||||||
|
sched.mark_test_pending(crashitem)
|
||||||
|
report.outcome = "rerun"
|
||||||
|
|
||||||
|
.. versionadded:: 2.2.1
|
||||||
|
"""
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ class EachScheduling:
|
|||||||
def mark_test_complete(self, node, item_index, duration=0):
|
def mark_test_complete(self, node, item_index, duration=0):
|
||||||
self.node2pending[node].remove(item_index)
|
self.node2pending[node].remove(item_index)
|
||||||
|
|
||||||
|
def mark_test_pending(self, item):
|
||||||
|
self.pending.insert(
|
||||||
|
0,
|
||||||
|
self.collection.index(item),
|
||||||
|
)
|
||||||
|
for node in self.node2pending:
|
||||||
|
self.check_schedule(node)
|
||||||
|
|
||||||
def remove_node(self, node):
|
def remove_node(self, node):
|
||||||
# KeyError if we didn't get an add_node() yet
|
# KeyError if we didn't get an add_node() yet
|
||||||
pending = self.node2pending.pop(node)
|
pending = self.node2pending.pop(node)
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ class LoadScheduling:
|
|||||||
self.node2pending[node].remove(item_index)
|
self.node2pending[node].remove(item_index)
|
||||||
self.check_schedule(node, duration=duration)
|
self.check_schedule(node, duration=duration)
|
||||||
|
|
||||||
|
def mark_test_pending(self, item):
|
||||||
|
self.pending.insert(
|
||||||
|
0,
|
||||||
|
self.collection.index(item),
|
||||||
|
)
|
||||||
|
for node in self.node2pending:
|
||||||
|
self.check_schedule(node)
|
||||||
|
|
||||||
def check_schedule(self, node, duration=0):
|
def check_schedule(self, node, duration=0):
|
||||||
"""Maybe schedule new items on the node
|
"""Maybe schedule new items on the node
|
||||||
|
|
||||||
|
|||||||
@@ -243,6 +243,9 @@ class LoadScopeScheduling:
|
|||||||
self.assigned_work[node][scope][nodeid] = True
|
self.assigned_work[node][scope][nodeid] = True
|
||||||
self._reschedule(node)
|
self._reschedule(node)
|
||||||
|
|
||||||
|
def mark_test_pending(self, item):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _assign_work_unit(self, node):
|
def _assign_work_unit(self, node):
|
||||||
"""Assign a work unit to a node."""
|
"""Assign a work unit to a node."""
|
||||||
assert self.workqueue
|
assert self.workqueue
|
||||||
|
|||||||
@@ -60,3 +60,37 @@ class TestHooks:
|
|||||||
["*HOOK: gw0 test_a, test_b, test_c", "*HOOK: gw1 test_a, test_b, test_c"]
|
["*HOOK: gw0 test_a, test_b, test_c", "*HOOK: gw1 test_a, test_b, test_c"]
|
||||||
)
|
)
|
||||||
res.stdout.fnmatch_lines(["*3 passed*"])
|
res.stdout.fnmatch_lines(["*3 passed*"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestCrashItem:
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def create_test_file(self, testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
def test_a(): pass
|
||||||
|
def test_b(): os._exit(1)
|
||||||
|
def test_c(): pass
|
||||||
|
def test_d(): pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_handlecrashitem(self, testdir):
|
||||||
|
"""Test pytest_handlecrashitem hook."""
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
test_runs = 0
|
||||||
|
|
||||||
|
def pytest_handlecrashitem(crashitem, report, sched):
|
||||||
|
global test_runs
|
||||||
|
|
||||||
|
if test_runs == 0:
|
||||||
|
sched.mark_test_pending(crashitem)
|
||||||
|
test_runs = 1
|
||||||
|
else:
|
||||||
|
print("HOOK: pytest_handlecrashitem")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
res = testdir.runpytest("-n2", "-s")
|
||||||
|
res.stdout.fnmatch_lines_random(["*HOOK: pytest_handlecrashitem"])
|
||||||
|
res.stdout.fnmatch_lines(["*3 passed*"])
|
||||||
|
|||||||
Reference in New Issue
Block a user