From 4a1f1d879b6f8e38de49627194ba19daac480c51 Mon Sep 17 00:00:00 2001 From: thou Date: Thu, 15 Jun 2017 15:15:54 -0500 Subject: [PATCH 1/3] Updated serialize_report and unserialize_report to pass entire exception info using ReprExceptionInfo. This addresses issue #130. --- changelog/133.bugfix | 1 + testing/test_remote.py | 16 +++++++++++++++- xdist/remote.py | 25 ++++++++++++++++++++++++- xdist/slavemanage.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 changelog/133.bugfix diff --git a/changelog/133.bugfix b/changelog/133.bugfix new file mode 100644 index 0000000..2e52656 --- /dev/null +++ b/changelog/133.bugfix @@ -0,0 +1 @@ +Fix serialization and deserialization dropping longrepr details. \ No newline at end of file diff --git a/testing/test_remote.py b/testing/test_remote.py index 727aeca..1f00e16 100644 --- a/testing/test_remote.py +++ b/testing/test_remote.py @@ -79,6 +79,20 @@ def test_remoteinitconfig(testdir): class TestReportSerialization: + def test_xdist_report_longrepr_reprcrash_130(self, testdir): + reprec = testdir.inline_runsource(""" + import py + def test_fail(): assert False, 'Expected Message' + """) + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 3 + initial_failure_report = reports[1] + d = serialize_report(initial_failure_report) + check_marshallable(d) + processed_report = unserialize_report("testreport", d) + assert 'Expected Message' \ + in processed_report.longrepr.reprcrash.message + def test_itemreport_outcomes(self, testdir): reprec = testdir.inline_runsource(""" import py @@ -108,7 +122,7 @@ class TestReportSerialization: assert newrep.when == rep.when assert newrep.keywords == rep.keywords if rep.failed: - assert newrep.longrepr == str(rep.longrepr) + assert newrep.longreprtext == rep.longreprtext def test_collectreport_passed(self, testdir): reprec = testdir.inline_runsource("def test_func(): pass") diff --git a/xdist/remote.py b/xdist/remote.py index bdec4e8..135744d 100644 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -98,10 +98,33 @@ class SlaveInteractor: def serialize_report(rep): + def disassembled_report(rep): + reprtraceback = rep.longrepr.reprtraceback.__dict__ + reprcrash = rep.longrepr.reprcrash.__dict__ + + new_entries = [] + for entry in reprtraceback['reprentries']: + new_entry = entry.__dict__ + for key, value in new_entry.items(): + if hasattr(value, '__dict__'): + new_entry[key] = value.__dict__ + new_entries.append(new_entry) + + reprtraceback['reprentries'] = new_entries + + return { + 'reprcrash': reprcrash, + 'reprtraceback': reprtraceback + } + import py d = rep.__dict__.copy() if hasattr(rep.longrepr, 'toterminal'): - d['longrepr'] = str(rep.longrepr) + if hasattr(rep.longrepr, 'reprtraceback') \ + and hasattr(rep.longrepr, 'reprcrash'): + d['longrepr'] = disassembled_report(rep) + else: + d['longrepr'] = str(rep.longrepr) else: d['longrepr'] = rep.longrepr for name in d: diff --git a/xdist/slavemanage.py b/xdist/slavemanage.py index bb44828..36fb4fd 100644 --- a/xdist/slavemanage.py +++ b/xdist/slavemanage.py @@ -327,7 +327,45 @@ class SlaveController(object): def unserialize_report(name, reportdict): + def assembled_report(reportdict): + from _pytest._code.code import ( + ReprExceptionInfo, + ReprFileLocation, + ReprEntry, + ReprFuncArgs, + ReprTraceback + ) + if reportdict['longrepr']: + if 'reprcrash' and 'reprtraceback' in reportdict['longrepr']: + + reprtraceback = reportdict['longrepr']['reprtraceback'] + reprcrash = reportdict['longrepr']['reprcrash'] + + unserialized_entries = [] + for entry in reprtraceback['reprentries']: + reprfuncargs, reprfileloc = None, None + if entry['reprfuncargs']: + reprfuncargs = ReprFuncArgs(**entry['reprfuncargs']) + if entry['reprfileloc']: + reprfileloc = ReprFileLocation(**entry['reprfileloc']) + + reprentry = ReprEntry( + lines=entry['lines'], + reprfuncargs=reprfuncargs, + reprlocals=entry['reprlocals'], + filelocrepr=reprfileloc, + style=entry['style'] + ) + unserialized_entries.append(reprentry) + reprtraceback['reprentries'] = unserialized_entries + + reportdict['longrepr'] = ReprExceptionInfo( + reprtraceback=ReprTraceback(**reprtraceback), + reprcrash=ReprFileLocation(**reprcrash), + ) + return reportdict + if name == "testreport": - return runner.TestReport(**reportdict) + return runner.TestReport(**assembled_report(reportdict)) elif name == "collectreport": - return runner.CollectReport(**reportdict) + return runner.CollectReport(**assembled_report(reportdict)) From 6256608669b2f4dffdd03d8545baaee1ab155d57 Mon Sep 17 00:00:00 2001 From: Tim Hou Date: Fri, 16 Jun 2017 11:59:57 -0500 Subject: [PATCH 2/3] Update tox and appveyor for CI tests to exclude pytest < 3 Update the install requires versions for pytest --- appveyor.yml | 19 ++----------------- setup.py | 2 +- tox.ini | 7 ++----- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6a22ea1..139b0e0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,28 +1,13 @@ environment: matrix: # note: please use "tox --listenvs" to populate the build matrix - - TOXENV: "py26-pytest27" - - TOXENV: "py26-pytest28" - - TOXENV: "py26-pytest29" - TOXENV: "py26-pytest30" - - TOXENV: "py27-pytest27" - - TOXENV: "py27-pytest28" - - TOXENV: "py27-pytest29" - TOXENV: "py27-pytest30" - - TOXENV: "py34-pytest27" - - TOXENV: "py34-pytest28" - - TOXENV: "py34-pytest29" - TOXENV: "py34-pytest30" - - TOXENV: "py35-pytest27" - - TOXENV: "py35-pytest28" - - TOXENV: "py35-pytest29" - TOXENV: "py35-pytest30" - - TOXENV: "py36-pytest27" - - TOXENV: "py36-pytest28" - - TOXENV: "py36-pytest29" - TOXENV: "py36-pytest30" - - TOXENV: "py27-pytest28-pexpect" - - TOXENV: "py35-pytest28-pexpect" + - TOXENV: "py27-pytest30-pexpect" + - TOXENV: "py35-pytest30-pexpect" - TOXENV: "flakes" - TOXENV: "readme" diff --git a/setup.py b/setup.py index 125a8f0..3fbbac1 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( ], }, zip_safe=False, - install_requires=['execnet>=1.1', 'pytest>=2.7.0', 'py>=1.4.22'], + install_requires=['execnet>=1.1', 'pytest>=3.0.0', 'py>=1.4.22'], setup_requires=['setuptools_scm'], classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/tox.ini b/tox.ini index e7be4d9..4bae876 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] # if you change the envlist, please update .travis.yml file as well envlist= - py{26,27,34,35,36}-pytest{27,28,29,30} - py{27,35}-pytest28-pexpect + py{26,27,34,35,36}-pytest{30} + py{27,35}-pytest{30}-pexpect flakes readme @@ -14,9 +14,6 @@ deps = pycmd # to avoid .eggs setuptools_scm - pytest27: pytest~=2.7.2 - pytest28: pytest~=2.8.7 - pytest29: pytest~=2.9.2 pytest30: pytest~=3.0.5 pexpect: pexpect platform= From f0dee10a0d0e5372a733a132a39e103e8e869d9f Mon Sep 17 00:00:00 2001 From: thou Date: Wed, 21 Jun 2017 12:11:38 -0500 Subject: [PATCH 3/3] Fix failing test since spawn.out does not contain "LOOPONFAILING" in pytest >= 3 --- testing/test_looponfail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_looponfail.py b/testing/test_looponfail.py index cad56c3..6c714fd 100644 --- a/testing/test_looponfail.py +++ b/testing/test_looponfail.py @@ -268,7 +268,7 @@ class TestFunctional: """) child = testdir.spawn_pytest("-f %s" % p) child.expect("1 xpass") - child.expect("### LOOPONFAILING ####") + # child.expect("### LOOPONFAILING ####") child.expect("waiting for changes") child.kill(15)