简体   繁体   English

pytest-timeout - 测试失败而不是终止整个测试运行

[英]pytest-timeout - fail test instead killing whole test run

I know in pytest-timeout I can specify timeout for each testcase but single failure terminates whole test run instead failing the slacking off testcase.我知道在pytest-timeout我可以为每个测试用例指定超时,但是单个失败会终止整个测试运行,而不是让松弛的测试用例失败。

Am I forced to make my own solution of this or there are ready-to-use tools which provide that?我是否被迫为此制定自己的解决方案,或者是否有现成的工具可以提供该解决方案?

I looked into this issue a long time ago and also came to the conclusion that a self-made solution would be better.我很久以前研究过这个问题,也得出结论,自制的解决方案会更好。

My plugin was killing the whole pytest process, but it can be adjusted to fail only a single (current) test easily.我的插件正在杀死整个 pytest 过程,但可以对其进行调整,使其仅在单个(当前)测试中失败。 Here is the adjusted draft:这是调整后的草案:

import pytest
import signal


class Termination(SystemExit):
    pass


class TimeoutExit(BaseException):
    pass


def _terminate(signum, frame):
    raise Termination("Runner is terminated from outside.")


def _timeout(signum, frame):
    raise TimeoutExit("Runner timeout is reached, runner is terminating.")


@pytest.hookimpl
def pytest_addoption(parser):
    parser.addoption(
        '--timeout', action='store', dest='timeout', type=int, default=None,
        help="number of seconds before each test failure")


@pytest.hookimpl
def pytest_configure(config):
    # Install the signal handlers that we want to process.
    signal.signal(signal.SIGTERM, _terminate)
    signal.signal(signal.SIGALRM, _timeout)


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):

    # Set the per-test timeout (an alarm signal).
    if item.config.option.timeout is not None:
        signal.alarm(item.config.option.timeout)

    try:
        # Run the setup, test body, and teardown stages.
        yield
    finally:
        # Disable the alarm when the test passes or fails.
        # I.e. when we get into the framework's body.
        signal.alarm(0)

When you do kill -ALRM $pid , or when each test times out individually due to the preset alarm, only the current test will fail, but the other tests will continue.当你执行kill -ALRM $pid ,或者当每个测试由于预设警报而单独超时时,只有当前测试会失败,但其他测试会继续。

And this TimeoutExit will not be suppressed by the libraries which do except Exception: pass because it inherits from BaseException .并且这个TimeoutExit不会被except Exception: pass的库抑制,因为它继承自BaseException

So, it is alike SystemExit in this aspect.所以,它在这方面类似于SystemExit However, unlike SystemExit or KeyboardInterruption , pytest will not catch it, and will not exit on such an exception.但是,与SystemExitKeyboardInterruption不同,pytest 不会捕获它,并且不会在出现此类异常时退出。

The exception will be injected into wherever the test does at the moment of the alarm, even if it does time.sleep(...) (so as for any signals).异常将被注入到警报发生时测试执行的任何地方,即使它执行time.sleep(...) (对于任何信号也是如此)。

Remember, that you can only have one single alarm set for the process (OS limitation).请记住,您只能为该过程设置一个警报(操作系统限制)。 Which also makes it incompatible with pytest-timeout , because it also uses the ALRM signal for the same purpose.这也使它与pytest-timeout不兼容,因为它也出于同样的目的使用 ALRM 信号。

If you want to have the global & per-test timeouts, you have to implement you smart alarm manager, which will keep track of few alarms, set the OS alarm to the earliest one, and decide which handler to call when the alarm signal is received.如果您想拥有全局和每次测试超时,则必须实现智能警报管理器,它将跟踪少数警报,将操作系统警报设置为最早的警报,并决定在警报信号出现时调用哪个处理程序收到。


In case, when you do kill -TERM $pid or just kill $pid (graceful termination), it will be terminated immediately — because it inherits from SystemExit , which is the BaseException and is usually not caught by the code or by pytest.如果您执行kill -TERM $pid或只是kill $pid (正常终止),它将立即终止 - 因为它继承自SystemExit ,这是BaseException并且通常不会被代码或 pytest 捕获。

The latter case mostly demonstrates how you can set different reactions to different signals.后一种情况主要展示了如何对不同的信号设置不同的反应。 You can do the similar things with USR1 & USR2 and other catchable signals.你可以用 USR1 和 USR2 以及其他可捕获的信号做类似的事情。


For a quick-test, put the plugin code above to the conftest.py file (a pseudo-plugin).为了快速测试,将上面的插件代码放在conftest.py文件(一个伪插件)中。

Consider this test file:考虑这个测试文件:

import time

def test_this():
    try:
        time.sleep(10)
    except Exception:
        pass

def test_that():
    pass

Running pytest without a timeout does nothing, and both tests pass:在没有超时的情况下运行 pytest 什么也不做,两个测试都通过了:

$ pytest -s -v
.........
collected 2 items                                                                                                                                                                 

test_me.py::test_this PASSED
test_me.py::test_that PASSED

======= 2 passed in 10.02 seconds =======

Running it with the timeout fail the first test, but pass the second one:使用超时运行它无法通过第一个测试,但通过第二个测试:

$ pytest -s -v --timeout=5
.........
collected 2 items                                                                                                                                                                 

test_me.py::test_this FAILED
test_me.py::test_that PASSED

============== FAILURES ==============
______________ test_this _____________

    def test_this():
        try:
>           time.sleep(10)

test_me.py:5: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

signum = 14, frame = <frame object at 0x106b3c428>

    def _timeout(signum, frame):
>       raise TimeoutExit("Runner timeout is reached, runner is terminating.")
E       conftest.pytest_configure.<locals>.TimeoutExit: Runner timeout is reached, runner is terminating.

conftest.py:24: TimeoutExit
======= 1 failed, 1 passed in 5.11 seconds =======

This has been fully supported by pytest-timeout right from the beginning, you want to use the signal method as described in the readme of pytest-timeout. pytest-timeout 从一开始就完全支持这一点,您想使用 pytest-timeout 自述文件中描述的signal方法。 Please do read the readme carefully as it comes with some caveats.请仔细阅读自述文件,因为它带有一些警告。 And indeed it is implemented using SIGALRM as the other answer also suggests, but it already exists so no need to re-do this.事实上,它是使用 SIGALRM 实现的,正如另一个答案所暗示的那样,但它已经存在,因此无需重新执行此操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM