diff --git a/changelog/196.bugfix b/changelog/196.bugfix new file mode 100644 index 0000000..1e7c1fe --- /dev/null +++ b/changelog/196.bugfix @@ -0,0 +1 @@ +Fix serialization of native tracebacks (``--tb=native``). diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 1a51fae..557464f 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -671,6 +671,20 @@ def test_worker_id_fixture(testdir, n): assert worker_ids == set(['gw0', 'gw1']) +@pytest.mark.parametrize('tb', + ['auto', 'long', 'short', 'no', 'line', 'native']) +def test_error_report_styles(testdir, tb): + testdir.makepyfile(""" + import pytest + def test_error_report_styles(): + raise RuntimeError('some failure happened') + """) + result = testdir.runpytest('-n1', '--tb=%s' % tb) + if tb != 'no': + result.stdout.fnmatch_lines('*some failure happened*') + result.assert_outcomes(failed=1) + + def test_color_yes_collection_on_non_atty(testdir, request): """skip collect progress report when working on non-terminals. diff --git a/testing/test_remote.py b/testing/test_remote.py index eeb5a4c..553b61c 100644 --- a/testing/test_remote.py +++ b/testing/test_remote.py @@ -113,11 +113,12 @@ class TestReportSerialization: assert added_section in a.longrepr.sections def test_reprentries_serialization_170(self, testdir): + from _pytest._code.code import ReprEntry reprec = testdir.inline_runsource(""" - def test_fail(): + def test_repr_entry(): x = 0 assert x - """, '--showlocals', '-n1') + """, '--showlocals') reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 rep = reports[1] @@ -128,6 +129,7 @@ class TestReportSerialization: a_entries = a.longrepr.reprtraceback.reprentries assert rep_entries == a_entries for i in range(len(a_entries)): + assert isinstance(rep_entries[i], ReprEntry) assert rep_entries[i].lines == a_entries[i].lines assert rep_entries[i].localssep == a_entries[i].localssep assert rep_entries[i].reprfileloc == a_entries[i].reprfileloc @@ -135,6 +137,26 @@ class TestReportSerialization: assert rep_entries[i].reprlocals == a_entries[i].reprlocals assert rep_entries[i].style == a_entries[i].style + def test_reprentries_serialization_196(self, testdir): + from _pytest._code.code import ReprEntryNative + reprec = testdir.inline_runsource(""" + def test_repr_entry_native(): + x = 0 + assert x + """, '--tb=native') + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 3 + rep = reports[1] + d = serialize_report(rep) + a = unserialize_report("testreport", d) + + rep_entries = rep.longrepr.reprtraceback.reprentries + a_entries = a.longrepr.reprtraceback.reprentries + assert rep_entries == a_entries + for i in range(len(a_entries)): + assert isinstance(rep_entries[i], ReprEntryNative) + assert rep_entries[i].lines == a_entries[i].lines + def test_itemreport_outcomes(self, testdir): reprec = testdir.inline_runsource(""" import py diff --git a/xdist/remote.py b/xdist/remote.py index 02c155a..7c5ded7 100644 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -104,11 +104,14 @@ def serialize_report(rep): new_entries = [] for entry in reprtraceback['reprentries']: - new_entry = entry.__dict__ - for key, value in new_entry.items(): + entry_data = { + 'type': type(entry).__name__, + 'data': entry.__dict__, + } + for key, value in entry_data['data'].items(): if hasattr(value, '__dict__'): - new_entry[key] = value.__dict__ - new_entries.append(new_entry) + entry_data['data'][key] = value.__dict__ + new_entries.append(entry_data) reprtraceback['reprentries'] = new_entries diff --git a/xdist/slavemanage.py b/xdist/slavemanage.py index 36d5263..0e45a20 100644 --- a/xdist/slavemanage.py +++ b/xdist/slavemanage.py @@ -332,6 +332,7 @@ def unserialize_report(name, reportdict): def assembled_report(reportdict): from _pytest._code.code import ( ReprEntry, + ReprEntryNative, ReprExceptionInfo, ReprFileLocation, ReprFuncArgs, @@ -345,22 +346,36 @@ def unserialize_report(name, reportdict): reprcrash = reportdict['longrepr']['reprcrash'] unserialized_entries = [] - for entry in reprtraceback['reprentries']: - reprfuncargs, reprfileloc, reprlocals = None, None, None - if entry['reprfuncargs']: - reprfuncargs = ReprFuncArgs(**entry['reprfuncargs']) - if entry['reprfileloc']: - reprfileloc = ReprFileLocation(**entry['reprfileloc']) - if entry['reprlocals']: - reprlocals = ReprLocals(entry['reprlocals']['lines']) + reprentry = None + for entry_data in reprtraceback['reprentries']: + data = entry_data['data'] + entry_type = entry_data['type'] + if entry_type == 'ReprEntry': + reprfuncargs = None + reprfileloc = None + reprlocals = None + if data['reprfuncargs']: + reprfuncargs = ReprFuncArgs( + **data['reprfuncargs']) + if data['reprfileloc']: + reprfileloc = ReprFileLocation( + **data['reprfileloc']) + if data['reprlocals']: + reprlocals = ReprLocals( + data['reprlocals']['lines']) - reprentry = ReprEntry( - lines=entry['lines'], - reprfuncargs=reprfuncargs, - reprlocals=reprlocals, - filelocrepr=reprfileloc, - style=entry['style'] - ) + reprentry = ReprEntry( + lines=data['lines'], + reprfuncargs=reprfuncargs, + reprlocals=reprlocals, + filelocrepr=reprfileloc, + style=data['style'] + ) + elif entry_type == 'ReprEntryNative': + reprentry = ReprEntryNative(data['lines']) + else: + report_unserialization_failure( + entry_type, name, reportdict) unserialized_entries.append(reprentry) reprtraceback['reprentries'] = unserialized_entries @@ -378,3 +393,17 @@ def unserialize_report(name, reportdict): return runner.TestReport(**assembled_report(reportdict)) elif name == "collectreport": return runner.CollectReport(**assembled_report(reportdict)) + + +def report_unserialization_failure(type_name, report_name, reportdict): + from pprint import pprint + url = 'https://github.com/pytest-dev/pytest-xdist/issues' + stream = py.io.TextIO() + pprint('-' * 100, stream=stream) + pprint('INTERNALERROR: Unknown entry type returned: %s' % type_name, + stream=stream) + pprint('report_name: %s' % report_name, stream=stream) + pprint(reportdict, stream=stream) + pprint('Please report this bug at %s' % url, stream=stream) + pprint('-' * 100, stream=stream) + assert 0, stream.getvalue()