Merge pull request #6412 from nicoddemus/remote-tb-5971

Fix serialization of 'None' reprcrashes
This commit is contained in:
Anthony Sottile 2020-01-07 11:08:28 -05:00 committed by GitHub
commit cff7843f3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 4 deletions

View File

@ -0,0 +1,2 @@
Fix a ``pytest-xdist`` crash when dealing with exceptions raised in subprocesses created by the
``multiprocessing`` module.

View File

@ -374,8 +374,11 @@ def _report_to_json(report):
]
return result
def serialize_repr_crash(reprcrash):
return reprcrash.__dict__.copy()
def serialize_repr_crash(reprcrash: Optional[ReprFileLocation]):
if reprcrash is not None:
return reprcrash.__dict__.copy()
else:
return None
def serialize_longrepr(rep):
result = {
@ -455,8 +458,11 @@ def _report_kwargs_from_json(reportdict):
]
return ReprTraceback(**repr_traceback_dict)
def deserialize_repr_crash(repr_crash_dict):
return ReprFileLocation(**repr_crash_dict)
def deserialize_repr_crash(repr_crash_dict: Optional[dict]):
if repr_crash_dict is not None:
return ReprFileLocation(**repr_crash_dict)
else:
return None
if (
reportdict["longrepr"]

View File

@ -305,6 +305,8 @@ class TestReportSerialization:
data = report._to_json()
loaded_report = report_class._from_json(data)
assert loaded_report.failed
check_longrepr(loaded_report.longrepr)
# make sure we don't blow up on ``toterminal`` call; we don't test the actual output because it is very
@ -312,6 +314,60 @@ class TestReportSerialization:
# elsewhere and we do check the contents of the longrepr object after loading it.
loaded_report.longrepr.toterminal(tw_mock)
def test_chained_exceptions_no_reprcrash(
self, testdir, tw_mock,
):
"""Regression test for tracebacks without a reprcrash (#5971)
This happens notably on exceptions raised by multiprocess.pool: the exception transfer
from subprocess to main process creates an artificial exception, which ExceptionInfo
can't obtain the ReprFileLocation from.
"""
testdir.makepyfile(
"""
from concurrent.futures import ProcessPoolExecutor
def func():
raise ValueError('value error')
def test_a():
with ProcessPoolExecutor() as p:
p.submit(func).result()
"""
)
reprec = testdir.inline_run()
reports = reprec.getreports("pytest_runtest_logreport")
def check_longrepr(longrepr):
assert isinstance(longrepr, ExceptionChainRepr)
assert len(longrepr.chain) == 2
entry1, entry2 = longrepr.chain
tb1, fileloc1, desc1 = entry1
tb2, fileloc2, desc2 = entry2
assert "RemoteTraceback" in str(tb1)
assert "ValueError: value error" in str(tb2)
assert fileloc1 is None
assert fileloc2.message == "ValueError: value error"
# 3 reports: setup/call/teardown: get the call report
assert len(reports) == 3
report = reports[1]
assert report.failed
check_longrepr(report.longrepr)
data = report._to_json()
loaded_report = TestReport._from_json(data)
assert loaded_report.failed
check_longrepr(loaded_report.longrepr)
# for same reasons as previous test, ensure we don't blow up here
loaded_report.longrepr.toterminal(tw_mock)
class TestHooks:
"""Test that the hooks are working correctly for plugins"""