When using pytest and verifying sys.exit() was called, or perhaps a SystemExit was directly raised, e.value.code contains the exit status only in a special case -- not in general.
BTW, we are discussing the "provisional" exit status, because, from https://docs.python.org/3/library/sys.html, trouble during Python cleanup could produce a different exit code.
The special case...
1. if sys.exit or SystemExit was called with a single int argument, like either of these statements
sys.exit(2)
raise SystemExit(2)
then e.value.code will match the exit status.
However, for these other cases, e.value.code WILL NOT match the exit status.
2. These statements, with no args
sys.exit()
raise SystemExit
raise SystemExit()
will produce an exit status of 0, but e.value.code == 0 is false.
3. These statements, with a string arg,
sys.exit('Trouble...')
raise SystemExit('Trouble...')
will produce an exit status of 1, but e.value.code == 1 is false.
Any other usage of sys.exit or raise SystemExit is irregular, but for completeness, for these usages e.value.code WILL NOT match the exit status.
4. passing in a single obj that is neither int or str, or passing
multiple args to the SystemExit call, like
sys.exit((4, 'Tribulation...'))
raise SystemExit(4, 'Tribulation)
will produce an exit status of 1, but e.value.code == 1 is false.
So, if you needed to make an assertion about the exit status, one approach
would be to assert exitstatus(e.value) == ...
and implement python's SystemExit logic in an exitstatus function, like
import re
import sys
import pytest
def exitstatus(value):
"""Determine the exit status based on the SystemExit object's attrs.
More accurately, this is the *provisional* exit status, because
trouble during Python cleanup could produce a different exit code.
Example
with pytest.raises(SystemExit) as e:
sys.exit(2)
assert exitstatus(e.value) == 2
Example
with pytest.raises(SystemExit) as e:
raise SystemExit('Trouble...')
assert exitstatus(e.value) == 1
"""
if len(value.args) == 0:
return 0
elif len(value.args) == 1 and isinstance(value.args[0], int):
return value.args[0]
elif len(value.args) == 1 and isinstance(value.args[0], str):
return 1
else:
# Irregular usage of sys.exit() or SystemExit will produce
# exit status 1.
# If you want to trigger on any irregular usage so you can
# fix it, you could raise an exception here, to force your
# unit test to fail.
# Otherwise, well, the irregular usage does result in exit
# status 1, so this is faithful.
return 1
def test_sysexit_0():
with pytest.raises(SystemExit) as e:
sys.exit(0) # exit status should be 0
assert exitstatus(e.value) == 0
def test_sysexit_2():
with pytest.raises(SystemExit) as e:
sys.exit(2) # exit status should be 2
assert exitstatus(e.value) == 2
def test_sysexit_noargs():
with pytest.raises(SystemExit) as e:
sys.exit() # exit status should be 0
assert exitstatus(e.value) == 0
def test_sysexit_msg():
with pytest.raises(SystemExit,
match='cmd: bad usage') as e:
sys.exit('cmd: bad usage unrecognized option') # exit status should be 1
assert exitstatus(e.value) == 1
With Best Regards,
John R.