Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f27265cd28 | ||
|
|
6b0a8c633b | ||
|
|
7f2ca0aecb | ||
|
|
64f8ad5469 | ||
|
|
144b37ae4b | ||
|
|
e41ba03edb | ||
|
|
cb3e734397 | ||
|
|
af8a5742ad | ||
|
|
d2d5ae9f5f | ||
|
|
05932011ed | ||
|
|
cd12d174c4 | ||
|
|
3652e21f3b | ||
|
|
a84b106a3b | ||
|
|
5d757f9db3 | ||
|
|
674c623ffa | ||
|
|
1fca8ee16d | ||
|
|
185595f596 | ||
|
|
55bc8c49d6 | ||
|
|
530c9b234a | ||
|
|
1ca9eff35d | ||
|
|
5320d25e47 | ||
|
|
96ad4d1cfd | ||
|
|
5c1a35fee4 | ||
|
|
665b8eda8d | ||
|
|
cee78e9b20 | ||
|
|
6e1154881d | ||
|
|
a96f6c9038 | ||
|
|
47b65716a3 | ||
|
|
dbaf49cd69 | ||
|
|
41c00ca47e | ||
|
|
f0dee10a0d | ||
|
|
6256608669 | ||
|
|
4a1f1d879b | ||
|
|
2094157950 | ||
|
|
7473683cdb | ||
|
|
c48834aa69 |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,8 +4,8 @@ Here's a quick checklist that should be present in PRs:
|
||||
|
||||
- [ ] Make sure to include reasonable tests for your change if necessary
|
||||
|
||||
- [ ] Add a new news fragment into the changelog folder, following these guidelines:
|
||||
* Name it `$issue_id.$type` for example `588.bug`
|
||||
- [ ] We use [towncrier](https://pypi.python.org/pypi/towncrier) for changelog management, so please add a *news* file into the `changelog` folder following these guidelines:
|
||||
* Name it `$issue_id.$type` for example `588.bugfix`;
|
||||
* If you don't have an issue_id change it to the PR id after creating it
|
||||
* Ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
|
||||
* Make sure to use full sentences with correct case and punctuation, for example:
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
pytest-xdist 1.18.2 (2017-07-28)
|
||||
================================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Removal of unnecessary dependency on incorrect version of py. (`#105
|
||||
<https://github.com/pytest-dev/pytest-xdist/issues/105>`_)
|
||||
|
||||
- Fix bug in internal event-loop error handler in the master node. This bug
|
||||
would shadow the original errors making extremely hard/impossible for users
|
||||
to diagnose the problem properly. (`#175 <https://github.com/pytest-
|
||||
dev/pytest-xdist/issues/175>`_)
|
||||
|
||||
|
||||
pytest-xdist 1.18.1 (2017-07-05)
|
||||
================================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fixed serialization of ``longrepr.sections`` during error reporting from
|
||||
workers. (`#171 <https://github.com/pytest-dev/pytest-xdist/issues/171>`_)
|
||||
|
||||
- Fix ``ReprLocal`` not being unserialized breaking --showlocals usages. (`#176
|
||||
<https://github.com/pytest-dev/pytest-xdist/issues/176>`_)
|
||||
|
||||
|
||||
pytest-xdist 1.18.0 (2017-06-26)
|
||||
================================
|
||||
|
||||
- ``pytest-xdist`` now requires ``pytest>=3.0.0``.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Add long option `--numprocesses` as alternative for `-n`. (#168)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fix serialization and deserialization dropping longrepr details. (#133)
|
||||
|
||||
|
||||
pytest-xdist 1.17.1 (2017-06-10)
|
||||
================================
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
include CHANGELOG
|
||||
include LICENSE
|
||||
include README.txt
|
||||
include setup.py
|
||||
include tox.ini
|
||||
graft testing
|
||||
prune .git
|
||||
10
README.rst
10
README.rst
@@ -44,10 +44,6 @@ Installation
|
||||
|
||||
Install the plugin with::
|
||||
|
||||
easy_install pytest-xdist
|
||||
|
||||
# or
|
||||
|
||||
pip install pytest-xdist
|
||||
|
||||
or use the package in develop/in-place mode with
|
||||
@@ -262,9 +258,3 @@ where the configuration file was found.
|
||||
.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist
|
||||
.. _`pytest-xdist repository`: https://github.com/pytest-dev/pytest-xdist
|
||||
.. _`pytest`: http://pytest.org
|
||||
|
||||
Issue and Bug Tracker
|
||||
------------------------
|
||||
|
||||
Please use the `pytest issue tracker <https://github.com/pytest-dev/pytest/issues>`_
|
||||
for reporting bugs in this plugin.
|
||||
|
||||
19
appveyor.yml
19
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"
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
{% endif %}
|
||||
{% if sections[section] %}
|
||||
{% for category, val in definitions.items() if category in sections[section] and category != 'trivial' %}
|
||||
{% for category, val in definitions.items() if category in sections[section] %}
|
||||
|
||||
{{ definitions[category]['name'] }}
|
||||
{{ underline * definitions[category]['name']|length }}
|
||||
|
||||
{% if definitions[category]['showcontent'] %}
|
||||
{% for text, values in sections[section][category]|dictsort(by='value') %}
|
||||
- {{ text }}{% if category != 'vendor' %} ({{ values|sort|join(', ') }}){% endif %}
|
||||
- {{ text }}{% if category != 'vendor' %} (`{{ values[0] }} <https://github.com/pytest-dev/pytest-xdist/issues/{{ values[0][1:] }}>`_){% endif %}
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
2
setup.py
2
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'],
|
||||
setup_requires=['setuptools_scm'],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ def test_dist_options(testdir):
|
||||
check_options(config)
|
||||
assert config.option.dist == "load"
|
||||
assert config.option.tx == ['popen'] * 2
|
||||
config = testdir.parseconfigure("--numprocesses", "2")
|
||||
check_options(config)
|
||||
assert config.option.dist == "load"
|
||||
assert config.option.tx == ['popen'] * 2
|
||||
config = testdir.parseconfigure("-d")
|
||||
check_options(config)
|
||||
assert config.option.dist == "load"
|
||||
|
||||
@@ -29,9 +29,9 @@ class EventCall:
|
||||
class SlaveSetup:
|
||||
use_callback = False
|
||||
|
||||
def __init__(self, request):
|
||||
self.testdir = request.getfuncargvalue("testdir")
|
||||
def __init__(self, request, testdir):
|
||||
self.request = request
|
||||
self.testdir = testdir
|
||||
self.events = queue.Queue()
|
||||
|
||||
def setup(self, ):
|
||||
@@ -65,8 +65,8 @@ class SlaveSetup:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def slave(request):
|
||||
return SlaveSetup(request)
|
||||
def slave(request, testdir):
|
||||
return SlaveSetup(request, testdir)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='#59')
|
||||
@@ -79,6 +79,62 @@ 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
|
||||
rep = reports[1]
|
||||
added_section = ('Failure Metadata', str("metadata metadata"), "*")
|
||||
rep.longrepr.sections.append(added_section)
|
||||
d = serialize_report(rep)
|
||||
check_marshallable(d)
|
||||
a = unserialize_report("testreport", d)
|
||||
# Check assembled == rep
|
||||
assert a.__dict__.keys() == rep.__dict__.keys()
|
||||
for key in rep.__dict__.keys():
|
||||
if key != 'longrepr':
|
||||
assert getattr(a, key) == getattr(rep, key)
|
||||
assert rep.longrepr.reprcrash.lineno == a.longrepr.reprcrash.lineno
|
||||
assert rep.longrepr.reprcrash.message == a.longrepr.reprcrash.message
|
||||
assert rep.longrepr.reprcrash.path == a.longrepr.reprcrash.path
|
||||
assert rep.longrepr.reprtraceback.entrysep \
|
||||
== a.longrepr.reprtraceback.entrysep
|
||||
assert rep.longrepr.reprtraceback.extraline \
|
||||
== a.longrepr.reprtraceback.extraline
|
||||
assert rep.longrepr.reprtraceback.style \
|
||||
== a.longrepr.reprtraceback.style
|
||||
assert rep.longrepr.sections == a.longrepr.sections
|
||||
assert rep.longrepr.reprtraceback.reprentries \
|
||||
== a.longrepr.reprtraceback.reprentries
|
||||
# Missing section attribute PR171
|
||||
assert added_section in a.longrepr.sections
|
||||
|
||||
def test_reprentries_serialization_170(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
def test_fail():
|
||||
x = 0
|
||||
assert x
|
||||
""", '--showlocals', '-n1')
|
||||
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 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
|
||||
assert rep_entries[i].reprfuncargs == a_entries[i].reprfuncargs
|
||||
assert rep_entries[i].reprlocals == a_entries[i].reprlocals
|
||||
assert rep_entries[i].style == a_entries[i].style
|
||||
|
||||
def test_itemreport_outcomes(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
import py
|
||||
@@ -108,7 +164,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")
|
||||
@@ -263,6 +319,15 @@ class TestSlaveInteractor:
|
||||
("pytest_collectreport", "report.collector.fspath == bbb"),
|
||||
])
|
||||
|
||||
def test_process_from_remote_error_handling(self, slave, capsys):
|
||||
slave.use_callback = True
|
||||
slave.setup()
|
||||
slave.slp.process_from_remote(('<nonono>', ()))
|
||||
out, err = capsys.readouterr()
|
||||
assert 'INTERNALERROR> ValueError: unknown event: <nonono>' in out
|
||||
ev = slave.popevent()
|
||||
assert ev.name == "errordown"
|
||||
|
||||
|
||||
def test_remote_env_vars(testdir):
|
||||
testdir.makepyfile('''
|
||||
|
||||
7
tox.ini
7
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=
|
||||
|
||||
@@ -20,7 +20,7 @@ def parse_numprocesses(s):
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("xdist", "distributed and subprocess testing")
|
||||
group._addoption(
|
||||
'-n', dest="numprocesses", metavar="numprocesses",
|
||||
'-n', '--numprocesses', dest="numprocesses", metavar="numprocesses",
|
||||
action="store",
|
||||
type=parse_numprocesses,
|
||||
help="shortcut for '--dist=load --tx=NUM*popen', "
|
||||
|
||||
@@ -98,10 +98,34 @@ 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,
|
||||
'sections': rep.longrepr.sections
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
@@ -323,11 +323,58 @@ class SlaveController(object):
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
py.builtin.print_("!" * 20, excinfo)
|
||||
self.config.pluginmanager.notify_exception(excinfo)
|
||||
self.config.notify_exception(excinfo)
|
||||
self.shutdown()
|
||||
self.notify_inproc("errordown", node=self, error=excinfo)
|
||||
|
||||
|
||||
def unserialize_report(name, reportdict):
|
||||
def assembled_report(reportdict):
|
||||
from _pytest._code.code import (
|
||||
ReprEntry,
|
||||
ReprExceptionInfo,
|
||||
ReprFileLocation,
|
||||
ReprFuncArgs,
|
||||
ReprLocals,
|
||||
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, 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 = ReprEntry(
|
||||
lines=entry['lines'],
|
||||
reprfuncargs=reprfuncargs,
|
||||
reprlocals=reprlocals,
|
||||
filelocrepr=reprfileloc,
|
||||
style=entry['style']
|
||||
)
|
||||
unserialized_entries.append(reprentry)
|
||||
reprtraceback['reprentries'] = unserialized_entries
|
||||
|
||||
exception_info = ReprExceptionInfo(
|
||||
reprtraceback=ReprTraceback(**reprtraceback),
|
||||
reprcrash=ReprFileLocation(**reprcrash),
|
||||
)
|
||||
|
||||
for section in reportdict['longrepr']['sections']:
|
||||
exception_info.addsection(*section)
|
||||
reportdict['longrepr'] = exception_info
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user