Lemmons / pytest-raises

An implementation of pytest.raises as a pytest.mark fixture
MIT License
19 stars 8 forks source link

Receive INTERNAL Error if no exception is thrown #29

Closed Xyphenore closed 1 year ago

Xyphenore commented 1 year ago

Hello, good plugin, I like it. I have an INTERNAL ERROR with pytest 7.2.2 - 7.3.0 with the plugin pytest-raises.

If I decorate a test with '@pytest.mark.raises(exception=Exception)', where Exception can be any type of exception. If this test finishes without raising an exception, pytest prints INTERNAL ERROR and stop the execution with code 3.

A basic example of broken test

import pytest

@pytest.mark.raises(exception=Exception)
def test_not_raises_exception() -> None:
    assert True
Traceback ``` INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/main.py", line 269, in wrap_session INTERNALERROR> session.exitstatus = doit(config, session) or 0 INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/main.py", line 323, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__ INTERNALERROR> return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 60, in _multicall INTERNALERROR> return outcome.get_result() INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall INTERNALERROR> res = hook_impl.function(*args) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/main.py", line 348, in pytest_runtestloop INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__ INTERNALERROR> return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 60, in _multicall INTERNALERROR> return outcome.get_result() INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall INTERNALERROR> res = hook_impl.function(*args) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/runner.py", line 114, in pytest_runtest_protocol INTERNALERROR> runtestprotocol(item, nextitem=nextitem) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/runner.py", line 133, in runtestprotocol INTERNALERROR> reports.append(call_and_report(item, "call", log)) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/runner.py", line 224, in call_and_report INTERNALERROR> report: TestReport = hook.pytest_runtest_makereport(item=item, call=call) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__ INTERNALERROR> return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 55, in _multicall INTERNALERROR> gen.send(outcome) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/skipping.py", line 266, in pytest_runtest_makereport INTERNALERROR> rep = outcome.get_result() INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall INTERNALERROR> res = hook_impl.function(*args) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/runner.py", line 368, in pytest_runtest_makereport INTERNALERROR> return TestReport.from_item_and_call(item, call) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/reports.py", line 363, in from_item_and_call INTERNALERROR> longrepr = item.repr_failure(excinfo) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/python.py", line 1833, in repr_failure INTERNALERROR> return self._repr_failure_py(excinfo, style=style) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/nodes.py", line 484, in _repr_failure_py INTERNALERROR> return excinfo.getrepr( INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/_code/code.py", line 671, in getrepr INTERNALERROR> return fmt.repr_excinfo(self) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/_code/code.py", line 987, in repr_excinfo INTERNALERROR> return ExceptionChainRepr(repr_chain) INTERNALERROR> File "/home/xyf/test_raises/.venv/lib/python3.9/site-packages/_pytest/_code/code.py", line 1039, in __init__ INTERNALERROR> reprtraceback=chain[-1][0], INTERNALERROR> IndexError: list index out of range Process finished with exit code 3 ```

The problem is present only if no exception is thrown. Then the exception raised line 84 'raise ExceptionClass(failure_message)' in '_pytest_raisesvalidation' is hidden because '__tracebackhide_\' is True. If in the function '_pytest_fail_by_mark_or_setexcinfo' '__tracebackhide_\' is set to False, pytest does not stop the execution, but the traceback is bad.

Bad Traceback ``` item = outcome = marker_name = 'raises' ExceptionClass = failure_message = "Expected exception , but it did not raise" traceback = None def _pytest_fail_by_mark_or_set_excinfo(item, outcome, marker_name, ExceptionClass, failure_message, traceback): """ Defer a test failure to a later stage, or set ``excinfo`` of ``outcome``, depending on ``marker_name``. This function should only be called for test items that have failed -- the end result of calling this function for *any* ``item`` / ``outcome`` is that the test will fail. .. warning:: **This is a "private" function not intended to be called directly by external projects!** Depending on the stage at which this function is called, one of two actions will be performed: 1. ``marker_name='setup_raises'``: a "secret" marker will be added to ``item`` indicating that the test failed. This marker is then checked at a later stage when it is safe to fail. See documentation for :func:`_pytest_raises_validation` for more information. 2. ``marker_name='raises'``: the ``outcome.excinfo`` will be populated with an exception traceback that will eventually (through ``pytest``) mark the test as failed. **Parameters** ``item`` The ``pytest`` test item, e.g., what is supplied to ``pytest_runtest_setup(item)`` or ``pytest_runtest_call(item)``. ``outcome`` The ``pytest`` test outcome for the ``@pytest.hookimpl(hookwrapper=True)`` hook wrappers, where ``outcome = yield``. ``marker_name`` The string marker name. Values are **assumed** to be ``'setup_raises'`` or ``'raises'`` **only**. - ``'setup_raises'``: call originates from ``pytest_runtest_setup`` hook wrapper. - ``'raises'``: call originates from ``pytest_runtest_call`` hook wrapper. ``ExceptionClass`` The exception class to re-raise. Expected to be :class:`ExpectedException` or :class:`ExpectedMessage`, but not strictly required. ``failure_message`` The string failure message to mark with or re-raise, depending on the value of ``marker_name``. ``traceback`` The traceback information if available, ``None`` otherwise. """ # pylint: disable=unused-variable __tracebackhide__ = False if marker_name == 'setup_raises': # In the later stage when `fail` is called, it is nice to "simulate" an # exception by putting the expected exception class's name as a prefix. failure_message = '{}: {}'.format(ExceptionClass.__name__, failure_message) item.add_marker(pytest.mark.setup_raises_expected_exc_or_message_not_found(failure_message)) else: # marker_name == 'raises' # Avoid "while handling exception another exception occurred" scenarios. if issubclass(ExceptionClass, PytestRaisesUsageError): failure_message = '{}: {}'.format(ExceptionClass.__name__, failure_message) pytest.fail(failure_message, pytrace=False) else: try: > raise ExceptionClass(failure_message) E pytest_raises.pytest_raises.ExpectedException: Expected exception , but it did not raise .venv/lib/python3.9/site-packages/pytest_raises/pytest_raises.py:84: ExpectedException ```

I don't know how to fix this problem.

I have installed this dependencies: Python = 3.9.5 coverage = "^7.1.0" pytest = "<8.0.0" pytest-asyncio = "<1.0.0" pytest-cov = "^4.0.0" pytest-raises = "<1.0.0" pytest-xdist = {extras = ["psutil"], version = "^3.2.1"} pytest-rich = "^0.1.1" pytest-timer = "^0.0.11" attrs = "^22.2.0"

Xyphenore commented 1 year ago

It's a pytest bug pytest-dev/pytest#10903. Fixed in pytest 7.3.1 https://github.com/pytest-dev/pytest/releases/tag/7.3.1