py.test: how to automatically detect an exception in a child process?
multiprocessing, pytest, python, testing
Solution
After some further consideration, I think that just catching the expected exception in the child and checking for the child's exit state is a very clean and reliable solution, that does not add an additional IPC component to the tests. Example code:
import multiprocessing
from py.test import raises
class CustomError(Exception):
pass
def test_mp_expected_fail():
p = multiprocessing.Process(target=child_expected_fail)
p.start()
p.join()
assert not p.exitcode
def test_mp_success():
p = multiprocessing.Process(target=child)
p.start()
p.join()
assert not p.exitcode
def test_mp_unexpected_fail():
p = multiprocessing.Process(target=child_unexpected_fail)
p.start()
p.join()
assert not p.exitcode
def child_expected_fail():
with raises(CustomError):
raise CustomError
def child_unexpected_fail():
raise TypeError
def child():
pass
Execution:
$ py.test pytest_mp_test.py
================================== test session starts ===================================
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
plugins: cov
collected 3 items
pytest_mp_test.py ..F
======================================== FAILURES ========================================
________________________________ test_mp_unexpected_fail _________________________________
def test_mp_unexpected_fail():
p = multiprocessing.Process(target=child_unexpected_fail)
p.start()
p.join()
> assert not p.exitcode
E assert not 1
E + where 1 = <Process(Process-3, stopped[1])>.exitcode
pytest_mp_test.py:23: AssertionError
------------------------------------ Captured stderr -------------------------------------
Process Process-3:
Traceback (most recent call last):
File "/apps11/bioinfp/Python-2.7.3/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/apps11/bioinfp/Python-2.7.3/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "/home/bioinfp/jang/hobbyproggorn/gevent-messagepipe/gevent-messagepipe/pytest_mp_test.py", line 31, in child_unexpected_fail
raise TypeError
TypeError
=========================== 1 failed, 2 passed in 0.07 seconds ===========================
Problem
I'm running py.test on Linux in the context of a module that makes heavy usage of multiprocessing. Exceptions in child processes are not detected as an error. Example test file `pytest_mp_test.py`: ``` import multiprocessing def test_mp(): p = multiprocessing.Process(target=child) p.start() p.join() def child(): assert False ``` Execution: ``` $ py.test pytest_mp_test.py ================================== test session starts =================================== platform linux2 -- Python 2.7.3 -- pytest-2.3.3 plugins: cov collected 1 items pytest_mp_test.py . ================================ 1 passed in 0.04 seconds ================================ ``` No error detected. The exception is printed when invoked with `-s`: ``` $ py.test -s pytest_mp_test.py ================================== test session starts =================================== platform linux2 -- Python 2.7.3 -- pytest-2.3.3 plugins: cov collected 1 items pytest_mp_test.py Process Process-1: Traceback (most recent call last): File "/apps11/bioinfp/Python-2.7.3/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap self.run() File "/apps11/bioinfp/Python-2.7.3/lib/python2.7/multiprocessing/process.py", line 114, in run self._target(*self._args, **self._kwargs) File "/home/bioinfp/jang/hobbyproggorn/gevent-messagepipe/gevent-messagepipe/pytest_mp_test.py", line 9, in child assert False AssertionError: assert False . ================================ 1 passed in 0.04 seconds ================================ ``` When I manually review the test log, I realize when there is a problem. However, I am wondering if there a neat way to automate exception detection in a child with py.test. Must I verify the child's exit code in the parent? Is this the only way?