Compare commits

...

34 Commits

Author SHA1 Message Date
Bruno Oliveira
adaa10f285 Release 1.20.1 2017-10-05 16:55:45 -03:00
Ronny Pfannschmidt
1cd7001647 Merge pull request #233 from blueyed/improve-pdb-usage-error
Improve UsageError when used with --pdb
2017-10-05 13:19:25 +02:00
Bruno Oliveira
95b660e26a Merge pull request #232 from verdesmarald/master
Don't create DSession in collect-only mode
2017-10-05 07:35:36 -03:00
Ronny Pfannschmidt
7a5efcdb76 Merge pull request #238 from timj/u/timj/exit-when-all-dead
Stop looping if all workers have died
2017-10-05 08:03:12 +02:00
Tim Jenness
9e59d07947 Stop looping if all workers have died
If the workers are crashing and the restart limit has been met,
we need to stop listening for events and trigger an internal
error.
2017-10-04 17:59:36 -07:00
Daniel Hahler
3799caa4fc Improve UsageError when used with --pdb
It might not be clear that pytest-xdist is used at all, and this should
help users to make --pdb work.
2017-09-23 21:07:39 +02:00
verdesmarald
1e66af6389 Fix flake8 formatting errors 2017-09-07 16:40:32 +10:00
verdesmarald
7674f6b186 Add tests for interactions between boxed, dist and collect-only.
Re-enable parsing of the boxed option with collect-only
2017-09-07 16:09:06 +10:00
verdesmarald
c83f73e19c Add news file for bugfix 2017-09-04 14:32:19 +10:00
verdesmarald
c874872190 Don't create DSession in collect-only mode 2017-09-04 14:25:42 +10:00
Ronny Pfannschmidt
e4f36b18f3 Merge pull request #228 from nicoddemus/ci-pytest
Add test environments using pytest master and features branches
2017-09-02 18:33:54 +02:00
Bruno Oliveira
0cdef59428 Add test environments using pytest master and features branches 2017-09-01 18:51:49 -03:00
Bruno Oliveira
4bb06e3f74 Merge pull request #227 from EdwardBetts/spelling
correct spelling mistake
2017-09-01 17:22:12 -03:00
Edward Betts
2c9b0b8894 correct spelling mistake 2017-09-01 20:45:18 +01:00
Bruno Oliveira
b464335c83 Fix link in CHANGELOG 2017-08-19 06:11:32 -03:00
Ronny Pfannschmidt
24b478446a Merge pull request #225 from pytest-dev/Ronny-patch-1
fix up travis deploy config
2017-08-18 15:47:25 +02:00
Ronny Pfannschmidt
d4d52dc933 fix up travis deploy config
move distributions key to the right place
install setuptools_scm for deploy
2017-08-18 14:00:44 +02:00
Bruno Oliveira
e5c2347934 Merge pull request #224 from nicoddemus/release-1.20.0
Release 1.20.0
2017-08-18 08:47:10 -03:00
Bruno Oliveira
577418f28c Prepare release 1.20.0 2017-08-17 18:34:03 -03:00
Bruno Oliveira
5c46cdf62d Mention flaky in the changelog for #206 2017-08-17 18:33:50 -03:00
Ronny Pfannschmidt
493ba6cb1d Merge pull request #223 from nicoddemus/travis-stages
Change Travis configuration to use stages in CI
2017-08-17 09:39:16 +02:00
Bruno Oliveira
d9bb158085 Change Travis configuration to use stages in CI
Fix #221
2017-08-16 23:54:38 -03:00
Ronny Pfannschmidt
0175605c57 Merge pull request #219 from nicoddemus/worker-id-docstring
Add docstring for worker_id
2017-08-11 15:49:26 +02:00
Bruno Oliveira
3ed405f22f Add docstring for worker_id
Fix #190
2017-08-10 21:51:30 -03:00
Bruno Oliveira
7b9546b89b Merge pull request #218 from nicoddemus/teardown-crash
Identify correct test crashed during teardown and support multiple test logs from plugins
2017-08-10 21:33:08 -03:00
Bruno Oliveira
542111d272 Merge pull request #217 from nicoddemus/release-1.19.1
Prepare release 1.19.1
2017-08-10 12:50:53 -03:00
Bruno Oliveira
2adf810e5e Add acceptance test for plugins which log tests multiple times
Fix #206
2017-08-10 11:18:52 -03:00
Bruno Oliveira
f8e138b986 Fix tests incorrectly identified if worker crashes during teardown stage
Fix #124
2017-08-10 10:43:40 -03:00
Bruno Oliveira
469c8c50d0 Prepare release 1.19.1 2017-08-10 07:17:59 -03:00
Ronny Pfannschmidt
79f1b6dab2 Merge pull request #215 from nicoddemus/logwarning-py-path
Fix crash when transferring internal pytest warnings from workers to the master node
2017-08-10 08:57:21 +02:00
Ronny Pfannschmidt
75e61d86b7 Merge pull request #213 from messa/patch-1
Fix reprcrash check in unserialize_report()
2017-08-10 08:55:51 +02:00
Bruno Oliveira
20a1b9ef11 Fix crash when transferring internal pytest warnings from workers to the master node
Fix #214
2017-08-09 17:55:16 -03:00
Bruno Oliveira
a54f531701 Merge pull request #212 from nicoddemus/release-1.19.0
Prepare release 1.19.0
2017-08-09 12:44:59 -03:00
Petr Messner
4389843dcb Fix reprcrash check in unserialize_report() 2017-08-09 17:44:45 +02:00
9 changed files with 244 additions and 56 deletions

View File

@@ -1,5 +1,16 @@
sudo: false sudo: false
language: python language: python
notifications:
irc:
channels:
- 'chat.freenode.net#pytest'
on_success: change
on_failure: change
skip_join: true
email:
- pytest-commit@python.org
python: python:
- '2.6' - '2.6'
- '2.7' - '2.7'
@@ -10,32 +21,39 @@ env:
- TOXENV=py-pytest30 - TOXENV=py-pytest30
- TOXENV=py-pytest31 - TOXENV=py-pytest31
- TOXENV=py-pytest32 - TOXENV=py-pytest32
install: pip install tox setuptools_scm
matrix:
include:
# note: please use "tox --listenvs" to populate the build matrix
- python: "3.6"
env: TOXENV=flakes
- python: "3.6"
env: TOXENV=readme
install: pip install tox setuptools_scm
script: tox script: tox
notifications:
irc: jobs:
channels: include:
- chat.freenode.net#pytest - stage: test
on_success: change # python x env above are already included into this stage
on_failure: change - python: "2.7"
skip_join: true env: TOXENV=py27-pytestmaster
email: - python: "2.7"
- pytest-commit@python.org env: TOXENV=py27-pytestfeatures
deploy: - python: "3.6"
provider: pypi env: TOXENV=py36-pytestmaster
user: ronny - python: "3.6"
skip_upload_docs: true env: TOXENV=py36-pytestfeatures
password: - python: "3.6"
secure: cxmSDho5d+PYKEM4ZCg8ms1P4lzhYkrw6fEOm2HtTcsuCyY6aZMSgImWAnEYbJHSkdzgcxlXK9UKJ9B0YenXmBCkAr7UjdnpNXNmkySr0sYzlH/sfqt/dDATCHFaRKxnkOSOVywaDYhT9n8YudbXI77pXwD12i/CeSSJDbHhsu0JYUfAcb+D6YjRYoA2SEGCnzSzg+gDDfwXZx4ZiODCGLVwieNp1klCg88YROUE1BaYYNuUOONvfXX8+TWowbCF6ChH1WL/bZ49OStEYQNuYxZQZr4yClIqu9VJbchrU8j860K9ott2kkGTgfB/dDrQB/XncBubyIX9ikzCQAmmBXWAI3eyvWLPDk2Jz7kW2l2RT7syct80tCq3JhvQ1qdwr5ap7siocTLgnBW0tF4tkHSTFN3510fkc43npnp6FThebESQpnI24vqpwJ9hI/kW5mYi014Og2E/cpCXnz2XO8iZPDbqAMQpDsqEQoyhfGNgPTGp4K30TxRtwZBI5hHhDKnnR16fXtRgt1gYPvz/peUQvvpOm4JzIzGXPzluuutpnCBy75v5+oiwT3YRrLL/Meims9FtDDXL3qQubAE/ezIOOpm0N5XXV8DxIom8EN71yq5ab1tqhM+tBX7owRjy4FR4If2Q8feBdmTuh26DIQt/y+qSG8VkB9Sw/JCjc7c= env: TOXENV=flakes
on: - python: "3.6"
tags: true env: TOXENV=readme
distributions: sdist bdist_wheel
repo: pytest-dev/pytest-xdist - stage: deploy
python: '3.6'
env:
install: pip install -U setuptools setuptools_scm
script: skip
deploy:
provider: pypi
user: ronny
distributions: sdist bdist_wheel
skip_upload_docs: true
password:
secure: cxmSDho5d+PYKEM4ZCg8ms1P4lzhYkrw6fEOm2HtTcsuCyY6aZMSgImWAnEYbJHSkdzgcxlXK9UKJ9B0YenXmBCkAr7UjdnpNXNmkySr0sYzlH/sfqt/dDATCHFaRKxnkOSOVywaDYhT9n8YudbXI77pXwD12i/CeSSJDbHhsu0JYUfAcb+D6YjRYoA2SEGCnzSzg+gDDfwXZx4ZiODCGLVwieNp1klCg88YROUE1BaYYNuUOONvfXX8+TWowbCF6ChH1WL/bZ49OStEYQNuYxZQZr4yClIqu9VJbchrU8j860K9ott2kkGTgfB/dDrQB/XncBubyIX9ikzCQAmmBXWAI3eyvWLPDk2Jz7kW2l2RT7syct80tCq3JhvQ1qdwr5ap7siocTLgnBW0tF4tkHSTFN3510fkc43npnp6FThebESQpnI24vqpwJ9hI/kW5mYi014Og2E/cpCXnz2XO8iZPDbqAMQpDsqEQoyhfGNgPTGp4K30TxRtwZBI5hHhDKnnR16fXtRgt1gYPvz/peUQvvpOm4JzIzGXPzluuutpnCBy75v5+oiwT3YRrLL/Meims9FtDDXL3qQubAE/ezIOOpm0N5XXV8DxIom8EN71yq5ab1tqhM+tBX7owRjy4FR4If2Q8feBdmTuh26DIQt/y+qSG8VkB9Sw/JCjc7c=
on:
tags: true
repo: pytest-dev/pytest-xdist

View File

@@ -1,3 +1,49 @@
pytest-xdist 1.20.1 (2017-10-05)
================================
Bug Fixes
---------
- Fix hang when all worker nodes crash and restart limit is reached (`#45
<https://github.com/pytest-dev/pytest-xdist/issues/45>`_)
- Fix issue where the -n option would still run distributed tests when pytest
was run with the --collect-only option (`#5
<https://github.com/pytest-dev/pytest-xdist/issues/5>`_)
pytest-xdist 1.20.0 (2017-08-17)
================================
Features
--------
- ``xdist`` now supports tests to log results multiple times, improving
integration with plugins which require it like `pytest-rerunfailures
<https://github.com/gocept/pytest-rerunfailures>`_ and `flaky
<https://pypi.python.org/pypi/flaky>`_. (`#206 <https://github.com/pytest-
dev/pytest-xdist/issues/206>`_)
Bug Fixes
---------
- Fix issue where tests were being incorrectly identified if a worker crashed
during the ``teardown`` stage of the test. (`#124 <https://github.com/pytest-
dev/pytest-xdist/issues/124>`_)
pytest-xdist 1.19.1 (2017-08-10)
================================
Bug Fixes
---------
- Fix crash when transferring internal pytest warnings from workers to the
master node. (`#214 <https://github.com/pytest-dev/pytest-
xdist/issues/214>`_)
pytest-xdist 1.19.0 (2017-08-09) pytest-xdist 1.19.0 (2017-08-09)
================================ ================================
@@ -218,7 +264,7 @@ Improved Documentation
in master. Thanks Aron Curzon. in master. Thanks Aron Curzon.
- fix pytest issue419 by sending/receiving indices into the test - fix pytest issue419 by sending/receiving indices into the test
collection instead of node ids (which are not neccessarily unique collection instead of node ids (which are not necessarily unique
for functions parametrized with duplicate values) for functions parametrized with duplicate values)
- send multiple "to test" indices in one network message to a slave - send multiple "to test" indices in one network message to a slave

View File

@@ -262,6 +262,17 @@ class TestDistribution:
child.close() child.close()
# assert ret == 2 # assert ret == 2
def test_dist_with_collectonly(self, testdir):
p1 = testdir.makepyfile("""
def test_ok():
pass
""")
result = testdir.runpytest(p1, "-n1", "--collect-only")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*collected 1 item*",
])
class TestDistEach: class TestDistEach:
def test_simple(self, testdir): def test_simple(self, testdir):
@@ -340,18 +351,28 @@ class TestTerminalReporting:
]) ])
@pytest.mark.parametrize('n', ['-n0', '-n1']) @pytest.mark.parametrize('n', ['-n0', '-n1'])
def test_logwarning(self, testdir, n): @pytest.mark.parametrize('warn_type', ['pytest', 'builtin'])
def test_logwarning(self, testdir, n, warn_type):
from pkg_resources import parse_version from pkg_resources import parse_version
if parse_version(pytest.__version__) < parse_version('3.1'): if parse_version(pytest.__version__) < parse_version('3.1'):
pytest.skip('pytest warnings requires >= 3.1') pytest.skip('pytest warnings requires >= 3.1')
if warn_type == 'builtin':
warn_code = """warnings.warn(UserWarning('this is a warning'))"""
elif warn_type == 'pytest':
warn_code = """request.config.warn('', 'this is a warning',
fslocation=py.path.local())"""
else:
assert False
testdir.makepyfile(""" testdir.makepyfile("""
import warnings import warnings, py
def test_func(): def test_func(request):
warnings.warn('this is a warning') {warn_code}
""") """.format(warn_code=warn_code))
result = testdir.runpytest(n) result = testdir.runpytest(n)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*this is a warning*", "*this is a warning*",
"*1 passed, 1 warnings*",
]) ])
@@ -461,18 +482,60 @@ def test_funcarg_teardown_failure(testdir):
assert result.ret assert result.ret
def test_crashing_item(testdir): @pytest.mark.parametrize('when', ['setup', 'call', 'teardown'])
def test_crashing_item(testdir, when):
"""Ensure crashing item is correctly reported during all testing stages"""
code = dict(setup='', call='', teardown='')
code[when] = 'py.process.kill(os.getpid())'
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import py
import os import os
def test_crash(): import py
py.process.kill(os.getpid()) import pytest
def test_noncrash():
@pytest.fixture
def fix():
{setup}
yield
{teardown}
def test_crash(fix):
{call}
pass pass
""")
def test_ok():
pass
""".format(**code))
passes = 2 if when == 'teardown' else 1
result = testdir.runpytest("-n2", p) result = testdir.runpytest("-n2", p)
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*crashed*test_crash*", "*1 failed*1 passed*" "*crashed*test_crash*",
"*1 failed*%d passed*" % passes,
])
def test_multiple_log_reports(testdir):
"""
Ensure that pytest-xdist supports plugins that emit multiple logreports
(#206).
Inspired by pytest-rerunfailures.
"""
testdir.makeconftest("""
from _pytest.runner import runtestprotocol
def pytest_runtest_protocol(item, nextitem):
item.ihook.pytest_runtest_logstart(nodeid=item.nodeid,
location=item.location)
reports = runtestprotocol(item, nextitem=nextitem)
for report in reports:
item.ihook.pytest_runtest_logreport(report=report)
return True
""")
testdir.makepyfile("""
def test():
pass
""")
result = testdir.runpytest("-n1")
result.stdout.fnmatch_lines([
"*2 passed*",
]) ])
@@ -649,6 +712,17 @@ class TestNodeFailure:
"*2 failed*2 passed*", "*2 failed*2 passed*",
]) ])
def test_max_slave_restart_die(self, testdir):
f = testdir.makepyfile("""
import os
os._exit(1)
""")
res = testdir.runpytest(f, '-n4', '--max-slave-restart=0')
res.stdout.fnmatch_lines([
"*Unexpectedly no active workers*",
"*INTERNALERROR*"
])
def test_disable_restart(self, testdir): def test_disable_restart(self, testdir):
f = testdir.makepyfile(""" f = testdir.makepyfile("""
import os import os

View File

@@ -44,6 +44,36 @@ def test_auto_detect_cpus(testdir, monkeypatch):
assert config.getoption('numprocesses') == 99 assert config.getoption('numprocesses') == 99
def test_boxed_with_collect_only(testdir):
from xdist.plugin import pytest_cmdline_main as check_options
config = testdir.parseconfigure("-n1", "--boxed")
check_options(config)
assert config.option.forked
config = testdir.parseconfigure("-n1", "--collect-only")
check_options(config)
assert not config.option.forked
config = testdir.parseconfigure("-n1", "--boxed", "--collect-only")
check_options(config)
assert config.option.forked
def test_dsession_with_collect_only(testdir):
from xdist.plugin import pytest_cmdline_main as check_options
from xdist.plugin import pytest_configure as configure
config = testdir.parseconfigure("-n1")
check_options(config)
configure(config)
assert config.pluginmanager.hasplugin("dsession")
config = testdir.parseconfigure("-n1", "--collect-only")
check_options(config)
configure(config)
assert not config.pluginmanager.hasplugin("dsession")
class TestDistOptions: class TestDistOptions:
def test_getxspecs(self, testdir): def test_getxspecs(self, testdir):
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz") config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")

View File

@@ -3,6 +3,7 @@
envlist= envlist=
py{26,27,34,35,36}-pytest{30,31,32} py{26,27,34,35,36}-pytest{30,31,32}
py{27,36}-pytest{30,31,32}-pexpect py{27,36}-pytest{30,31,32}-pexpect
py{27,36}-pytest{master,features}
flakes flakes
readme readme
@@ -17,6 +18,8 @@ deps =
pytest30: pytest~=3.0.5 pytest30: pytest~=3.0.5
pytest31: pytest~=3.1.0 pytest31: pytest~=3.1.0
pytest32: pytest~=3.2.0 pytest32: pytest~=3.2.0
pytestmaster: git+https://github.com/pytest-dev/pytest.git@master
pytestfeatures: git+https://github.com/pytest-dev/pytest.git@features
pexpect: pexpect pexpect: pexpect
platform= platform=
pexpect: linux|darwin pexpect: linux|darwin

View File

@@ -120,6 +120,10 @@ class DSession:
def loop_once(self): def loop_once(self):
"""Process one callback from one of the slaves.""" """Process one callback from one of the slaves."""
while 1: while 1:
if not self._active_nodes:
# If everything has died stop looping
self.triggershutdown()
raise RuntimeError("Unexpectedly no active workers available")
try: try:
eventcall = self.queue.get(timeout=2.0) eventcall = self.queue.get(timeout=2.0)
break break
@@ -230,18 +234,19 @@ class DSession:
nodeid=nodeid, location=location) nodeid=nodeid, location=location)
def slave_testreport(self, node, rep): def slave_testreport(self, node, rep):
"""Emitted when a node calls the pytest_runtest_logreport hook. """Emitted when a node calls the pytest_runtest_logreport hook."""
If the node indicates it is finished with a test item, remove
the item from the pending list in the scheduler.
"""
if rep.when == "call" or (rep.when == "setup" and not rep.passed):
self.sched.mark_test_complete(node, rep.item_index, rep.duration)
# self.report_line("testreport %s: %s" %(rep.id, rep.status))
rep.node = node rep.node = node
self.config.hook.pytest_runtest_logreport(report=rep) self.config.hook.pytest_runtest_logreport(report=rep)
self._handlefailures(rep) self._handlefailures(rep)
def slave_runtest_protocol_complete(self, node, item_index, duration):
"""
Emitted when a node fires the 'runtest_protocol_complete' event,
signalling that a test has completed the runtestprotocol and should be
removed from the pending list in the scheduler.
"""
self.sched.mark_test_complete(node, item_index, duration)
def slave_collectreport(self, node, rep): def slave_collectreport(self, node, rep):
"""Emitted when a node calls the pytest_collectreport hook.""" """Emitted when a node calls the pytest_collectreport hook."""
if rep.failed: if rep.failed:

View File

@@ -91,7 +91,7 @@ def pytest_addhooks(pluginmanager):
@pytest.mark.trylast @pytest.mark.trylast
def pytest_configure(config): def pytest_configure(config):
if config.getoption("dist") != "no": if config.getoption("dist") != "no" and not config.getvalue("collectonly"):
from xdist.dsession import DSession from xdist.dsession import DSession
session = DSession(config) session = DSession(config)
config.pluginmanager.register(session, "dsession") config.pluginmanager.register(session, "dsession")
@@ -115,7 +115,7 @@ def pytest_cmdline_main(config):
if val("dist") != "no": if val("dist") != "no":
if usepdb: if usepdb:
raise pytest.UsageError( raise pytest.UsageError(
"--pdb incompatible with distributing tests.") "--pdb is incompatible with distributing tests; try using -n0.") # noqa: E501
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# fixtures # fixtures
@@ -124,6 +124,9 @@ def pytest_cmdline_main(config):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def worker_id(request): def worker_id(request):
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
if running on the master node.
"""
if hasattr(request.config, 'slaveinput'): if hasattr(request.config, 'slaveinput'):
return request.config.slaveinput['slaveid'] return request.config.slaveinput['slaveid']
else: else:

View File

@@ -8,6 +8,7 @@
import sys import sys
import os import os
import time
import pytest import pytest
@@ -59,23 +60,29 @@ class SlaveInteractor:
self.log("items to run:", torun) self.log("items to run:", torun)
# only run if we have an item and a next item # only run if we have an item and a next item
while len(torun) >= 2: while len(torun) >= 2:
self.run_tests(torun) self.run_one_test(torun)
if name == "shutdown": if name == "shutdown":
if torun: if torun:
self.run_tests(torun) self.run_one_test(torun)
break break
return True return True
def run_tests(self, torun): def run_one_test(self, torun):
items = self.session.items items = self.session.items
self.item_index = torun.pop(0) self.item_index = torun.pop(0)
item = items[self.item_index]
if torun: if torun:
nextitem = items[torun[0]] nextitem = items[torun[0]]
else: else:
nextitem = None nextitem = None
start = time.time()
self.config.hook.pytest_runtest_protocol( self.config.hook.pytest_runtest_protocol(
item=items[self.item_index], item=item,
nextitem=nextitem) nextitem=nextitem)
duration = time.time() - start
self.sendevent("runtest_protocol_complete", item_index=self.item_index,
duration=duration)
def pytest_collection_finish(self, session): def pytest_collection_finish(self, session):
self.sendevent( self.sendevent(
@@ -99,7 +106,7 @@ class SlaveInteractor:
def pytest_logwarning(self, message, code, nodeid, fslocation): def pytest_logwarning(self, message, code, nodeid, fslocation):
self.sendevent("logwarning", message=message, code=code, nodeid=nodeid, self.sendevent("logwarning", message=message, code=code, nodeid=nodeid,
fslocation=fslocation) fslocation=str(fslocation))
def serialize_report(rep): def serialize_report(rep):

View File

@@ -315,6 +315,8 @@ class SlaveController(object):
self.notify_inproc(eventname, node=self, rep=rep) self.notify_inproc(eventname, node=self, rep=rep)
elif eventname == "collectionfinish": elif eventname == "collectionfinish":
self.notify_inproc(eventname, node=self, ids=kwargs['ids']) self.notify_inproc(eventname, node=self, ids=kwargs['ids'])
elif eventname == "runtest_protocol_complete":
self.notify_inproc(eventname, node=self, **kwargs)
elif eventname == "logwarning": elif eventname == "logwarning":
self.notify_inproc(eventname, message=kwargs['message'], self.notify_inproc(eventname, message=kwargs['message'],
code=kwargs['code'], nodeid=kwargs['nodeid'], code=kwargs['code'], nodeid=kwargs['nodeid'],
@@ -344,7 +346,7 @@ def unserialize_report(name, reportdict):
ReprTraceback ReprTraceback
) )
if reportdict['longrepr']: if reportdict['longrepr']:
if 'reprcrash' and 'reprtraceback' in reportdict['longrepr']: if 'reprcrash' in reportdict['longrepr'] and 'reprtraceback' in reportdict['longrepr']:
reprtraceback = reportdict['longrepr']['reprtraceback'] reprtraceback = reportdict['longrepr']['reprtraceback']
reprcrash = reportdict['longrepr']['reprcrash'] reprcrash = reportdict['longrepr']['reprcrash']