Search code examples
pythonmockingpytestsys

Stop current pytest execution but continue to run other cases


I am running tests using pytest on python.

In the tested code, some conditions trigger a sys.exit(non_zero_code), using pytest, we want to make sure that sys.exit is called under those conditions, but obviously we do not want the system to really exit, so we mock sys.

The problem is that now, the rest of the code will continue to execute, which I do not want. If I call pytest.exit, the whole process exit, when I just really want only that test case to stop at this point.

A good way to visualize is to read this simple example, the "Should not execute, we exited" line will be hit when we run the tests

import os
import sys

import pytest
import logging
from pathlib import Path

logging.basicConfig(filename=os.path.join(Path(__file__).parent.resolve(), "pytest_log"),
                    filemode='a',
                    format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
                    datefmt='%H:%M:%S',
                    level=logging.INFO)
logger = logging.getLogger(__name__)


def function_with_exit(value):

    if value == 2:
        sys.exit(2)
        logger.info("Should not execute, we exited")

    logger.info("Value is not 2")


def side_effect():
    logger.info("Sys exit was called")


@pytest.mark.parametrize("value", [1, 2, 3])
def test_with_pytest(mocker, value):
    mocker.patch(__name__ + '.' + sys.__name__)

    function_with_exit(value)

I need to modify the mock to:

  • Not hit the line when the second test case is run
  • Continue running the next test case

Solution

  • From python docs

    Since exit() ultimately “only” raises an exception, it will only exit the process when called from the main thread, and the exception is not intercepted. Cleanup actions specified by finally clauses of try statements are honored, and it is possible to intercept the exit attempt at an outer level.

    A call to sys.exit() will only create and raise a new SystemExit exception and raise it, This is not inherited from the usual Exception class but from the BaseException class, So it does not get caught with any regular exception handlers, instead they get escalated to the interpreter and interpreter cleans things neatly and exists.

    You can catch this in pytest and perform the needed asserts.

    with pytest.raises(SystemExit) as exc:
        function_with_exit()
        assert exc.value.code == 2