Skip to content

Commit

Permalink
Display exit code with InvocationErrors (#760)
Browse files Browse the repository at this point in the history
* Display exitcode upon InvocationError.

Issue #290.
Co-authored-by: Daniel Hahler <git@thequod.de>

* hint for exitcode > 128
* add changelog fragment
* use keyword argument instead of try/except
* add documentation
  • Loading branch information
ederag authored and gaborbernat committed Feb 7, 2018
1 parent 749588d commit c1db8a8
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 1 deletion.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Expand Up @@ -16,6 +16,7 @@ Chris Jerdonek
Chris Rose
Clark Boylan
Cyril Roelandt
Ederag
Eli Collins
Eugene Yunak
Fernando L. Pereira
Expand Down
1 change: 1 addition & 0 deletions changelog/290.feature.rst
@@ -0,0 +1 @@
``tox`` displays exit code together with ``InvocationError`` - by @blueyed and @ederag.
1 change: 1 addition & 0 deletions doc/example/basic.rst
Expand Up @@ -289,6 +289,7 @@ Integration with "setup.py test" command
point it's maybe best to not go for any ``setup.py test`` integration.


.. _`ignoring exit code`:

Ignoring a command exit code
----------------------------
Expand Down
46 changes: 46 additions & 0 deletions doc/example/general.rst
Expand Up @@ -204,3 +204,49 @@ There is an optimization coded in to not bother re-running the command if
``$projectname.egg-info`` is newer than ``setup.py`` or ``setup.cfg``.

.. include:: ../links.rst


Understanding ``InvocationError`` exit codes
--------------------------------------------

When a command (defined by ``commands =`` in ``tox.ini``) fails,
it has a non-zero exit code,
and an ``InvocationError`` exception is raised by ``tox``:

.. code-block:: shell
ERROR: InvocationError for command
'<command defined in tox.ini>' (exited with code 1)
If the command starts with ``pytest`` or ``python setup.py test`` for instance,
then the `pytest exit codes`_ are relevant.

On unix systems, there are some rather `common exit codes`_.
This is why for exit codes larger than 128, an additional hint is given:

.. code-block:: shell
ERROR: InvocationError for command
'<command defined in tox.ini>' (exited with code 139)
Note: On unix systems, an exit code larger than 128 often means a fatal error (e.g. 139=128+11: segmentation fault)
The signal numbers (e.g. 11 for a segmentation fault) can be found in the
"Standard signals" section of the `signal man page`_.
Their meaning is described in `POSIX signals`_.

Beware that programs may issue custom exit codes with any value,
so their documentation should be consulted.


Sometimes, no exit code is given at all.
An example may be found in `pytest-qt issue #170`_,
where Qt was calling ``abort()`` instead of ``exit()``.

.. seealso:: :ref:`ignoring exit code`.

.. _`pytest exit codes`: https://docs.pytest.org/en/latest/usage.html#possible-exit-codes
.. _`common exit codes`: http://www.faqs.org/docs/abs/HTML/exitcodes.html
.. _`abort()``: http://www.unix.org/version2/sample/abort.html
.. _`pytest-qt issue #170` : https://github.com/pytest-dev/pytest-qt/issues/170
.. _`signal man page`: http://man7.org/linux/man-pages/man7/signal.7.html
.. _`POSIX signals`: https://en.wikipedia.org/wiki/Signal_(IPC)#POSIX_signals
19 changes: 18 additions & 1 deletion tests/test_z_cmdline.py
Expand Up @@ -474,7 +474,7 @@ def test_package_install_fails(cmd, initproj):
})
result = cmd()
assert result.ret
assert result.outlines[-1].startswith('ERROR: python: InvocationError: ')
assert result.outlines[-1].startswith('ERROR: python: InvocationError for command ')


@pytest.fixture
Expand Down Expand Up @@ -904,3 +904,20 @@ def test_tox_quickstart_script():
def test_tox_cmdline(monkeypatch):
with pytest.raises(SystemExit):
tox.cmdline(['caller_script', '--help'])


@pytest.mark.parametrize('exitcode', [0, 5, 129])
def test_exitcode(initproj, cmd, exitcode):
tox_ini_content = "[testenv:foo]\ncommands=python -c 'import sys; sys.exit(%d)'" % exitcode
initproj("foo", filedefs={'tox.ini': tox_ini_content})
result = cmd()
if exitcode:
needle = "(exited with code %d)" % exitcode
assert any(needle in line for line in result.outlines)
if exitcode > 128:
needle = ("Note: On unix systems, an exit code larger than 128 "
"often means a fatal error (e.g. 139=128+11: segmentation fault)")
assert any(needle in line for line in result.outlines)
else:
needle = "(exited with code"
assert all(needle not in line for line in result.outlines)
13 changes: 13 additions & 0 deletions tox/__init__.py
Expand Up @@ -34,6 +34,19 @@ class InterpreterNotFound(Error):

class InvocationError(Error):
""" an error while invoking a script. """
def __init__(self, command, exitcode=None):
super(exception.Error, self).__init__(command, exitcode)
self.command = command
self.exitcode = exitcode

def __str__(self):
str_ = "%s for command %s" % (self.__class__.__name__, self.command)
if self.exitcode:
str_ += " (exited with code %d)" % (self.exitcode)
if self.exitcode > 128:
str_ += ("\nNote: On unix systems, an exit code larger than 128 "
"often means a fatal error (e.g. 139=128+11: segmentation fault)")
return str_

class MissingFile(Error):
""" an error while invoking a script. """
Expand Down

0 comments on commit c1db8a8

Please sign in to comment.