测试调用通过 Popen 运行的外部脚本,该脚本期望使用 pytest 在 stdin 中没有可用数据 [英] Test calling of an external script run via Popen that expects no available data in stdin using pytest

查看:49
本文介绍了测试调用通过 Popen 运行的外部脚本,该脚本期望使用 pytest 在 stdin 中没有可用数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有命令行界面的外部工具,它通过在 select.select([sys.stdin], [], [], 0)[ 中使用 sys.stdin 来检查是否通过 stdin 提供了任何数据0] 并相应地调整预期参数.我通过 subprocess 调用这个外部工具,并依赖于没有通过 stdin 提供输入的用例.

I have an external tool with a command line interface that checks whether any data is provided via stdin by using sys.stdin in select.select([sys.stdin], [], [], 0)[0] and adjusts the expected parameters accordingly. I call this external tool via subprocess and rely on the use case where there is no input provided via stdin.

现在我想通过 pytest 对此功能运行自动化集成测试.但是如果不提供 pytest 命令行选项 --capture=sys 禁用所有测试的文件描述符级别的捕获,我就无法让它工作.在任何其他情况下(也当在测试中的调用周围使用 capfd.disabled()capsys.disabled() 时,因为这些仅禁用 stdout 和 stderr 但不禁用 stdin 捕获),外部工具检测到会有通过stdin提供的输入,触发关于其他参数的错误结论,从而导致我的测试失败.

Now I want to run an automated integration test on this functionality via pytest. But I cannot get it to work without providing the pytest command line option --capture=sys disabling capturing on file descriptor level for all tests. In any other case (also when using capfd.disabled() or capsys.disabled() around the call within the test as these only disable stdout and stderr but not stdin capture), the external tool detects that there would be input provided via stdin, triggering the incorrect conclusions about other parameters and thus causing my test to fail.

举个例子,我基本上有以下文件:

For the sake of an example, I basically have something like the following files:

external_script.py:

external_script.py:

import select
import sys

print(sys.stdin in select.select([sys.stdin], [], [], 0)[0])
if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
    print(sys.stdin.readline().strip())
print(sys.argv)

internal_part.py:

internal_part.py:

import subprocess

def call_external():
    popen = subprocess.Popen(["/usr/bin/python3", "external_script.py", "1"],
                             stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    print(popen.communicate()[:2])

if __name__ == "__main__":
    call_external()

test.py:

import internal_part

def test(capsys):
    with capsys.disabled():  # or capfd.disabled()
        internal_part.call_external()

当我现在通过 python 运行 internal_part.py 时,我得到了预期的输出,sys.stdin 不是由对 select.select 的调用返回的,因此标准输入中没有可用的输入.但是当我通过 pytest 运行 test.py 而没有 --capture=sys 时,我得到的输出表明数据是通过标准输入提供的.从 sys.stdin 读取然后给出一个空字符串.

When I now run internal_part.py via python, I get the expected output, that sys.stdin is not returned by the call to select.select and thus that no input is available in stdin. But when I run test.py via pytest without --capture=sys, I get output indicating that data was provided via stdin. Reading from sys.stdin then gives an empty string.

如果 select.select 在内部部分调用 where,我至少会得到一个错误,指出这是伪文件作为重定向标准输入的不受支持的操作.对于在自己的解释器中运行的外部工具,除了检测到错误的 stdin 提供输入之外,我没有得到任何迹象表明出现问题.

If the select.select call where within the internal part, I at least would get an error that this is an unsupported operation of the pseudofile being the redirected stdin. For the external tool, running in its own interpreter, I don't get any indication that something went wrong except that erroneously stdin is detected to provide input.

是否有任何选项可以让这个测试场景工作,例如通过禁用此特定测试的 stdin 捕获,而不禁用该测试套件中所有测试的文件描述符级别的捕获?

Is there any option to get this test scenario to work, e.g. by disabling the capture of stdin for this specific test, without disabling the capture on the file descriptor level for all my tests in that test suite?

推荐答案

如果您愿意在生产代码中生成并显式传递尚未准备好读取的文件描述符1,您可以通过 os.pipe 生成一个新的命名管道,并将其读取端作为 stdin 传递.显然,对于奇怪的界面来说,生成一个新的管道,唯一的目的是它是空的,这在某种程度上是一种解决方法.但是只要您不向管道中写入任何内容,管道就不应该准备好读取,因此 select.select 调用不应该拾取它.内部部分可能看起来有点像下面的代码:

If you are willing to generate and explicitly pass a file descriptor that is not ready for reading in the production code1, you can generate a new named pipe via os.pipe and pass its reading end as stdin. Obviously, it is somewhat of a workaround for the strange interface to generate a new pipe with the sole purpose of it being empty. But as long as you don't write anything into the pipe, the pipe should not be ready for reading and thus the select.select call should not pick it up. The internal part could then look somewhat like the code below:

import contextlib
import os
import subprocess

@contextlib.contextmanager
def not_ready_to_read():
    try:
        read_end, write_end = os.pipe()
        yield read_end
    finally:
        os.close(read_end)
        os.close(write_end)

def call_external():
    with not_ready_to_read() as not_ready_stdin:
        popen = subprocess.Popen(["/usr/bin/python3", "external_script.py", "1"],
                                 stdin=not_ready_stdin, stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
        print(popen.communicate()[:2])

if __name__ == "__main__":
    call_external()

<小时>

1这也可以帮助您解决通过 select.select 进行的测试错误地表明某些数据通过 stdin 传递的其他情况,例如,如果您运行脚本通过 slurm 作业调度程序.因此,它不是以启用测试为唯一目的对生产代码的改编.


1This can also help you with other situations where the test via select.select erroneously suggests that some data is passed via stdin, such as if you run the script via the slurm job scheduler. Therefore, it's not an adaption of the production code with the sole purpose of enabling the test.

这篇关于测试调用通过 Popen 运行的外部脚本,该脚本期望使用 pytest 在 stdin 中没有可用数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆