运行 不使用 pytest no-capture 时子进程失败

Running a subprocess fails when not using pytest no-capture

设置

我已经把问题简化到核心了:

  1. 已安装 pytest
pip install pytest==5.4.3
  1. 有一个shell脚本
# has-stdin.sh

# Detect stdin
if [[ ! -t 0 ]]; then
    echo "yes"
else
    echo "no"
fi
  1. 进行 Python 测试
# test.py

import subprocess
import unittest


class TestCase(unittest.TestCase):

    def test(self):
        process = subprocess.run(
            ["sh", "./has-stdin.sh"],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            check=True,
            shell=False
        )
        assert process.stdout.decode("utf-8") == "no\n"


测试

✅ 脚本在 bash shell

中运行
$ sh ./has-stdin.sh
no
$ echo '' | sh ./has-stdin.sh
yes
$ sh ./has-stdin.sh <<< ''
yes

✅ 使用 -s 成功运行(例如 --capture=no

$ pytest test.py -s
platform linux -- Python 3.7.7, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/maikel/docker/library/postgresql
collected 1 item

test.py .

=========================== 1 passed in 0.02s ===========================

❌没有-s

运行不成功
$ pytest test.py
========================== test session starts ==========================
platform linux -- Python 3.7.7, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/maikel/docker/library/postgresql
collected 1 item

test.py F                                                                                                                                                                                                                      [100%]

================================ FAILURES ===============================
_____________________________ TestCase.test _____________________________

self = <test.TestCase testMethod=test>

    def test(self):
        process = subprocess.run(
            ["sh", "./has-stdin.sh"],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
            check=True,
            shell=False
        )
>       assert process.stdout.decode("utf-8") == "no\n"
E       AssertionError: assert 'yes\n' == 'no\n'
E         - no
E         + yes

test.py:23: AssertionError
======================== short test summary info ========================
FAILED test.py::TestCase::test - AssertionError: assert 'yes\n' == 'no\n'
=========================== 1 failed in 0.10s ===========================


使用 -s 有何不同?如何在没有 -s 的情况下成功 运行 这个 pytest?

我试过 ,但是当使用该方法时,两个 pytest 命令都失败了。

这是一个非常有趣的问题。

我非常感兴趣,所以我在本地做了 运行 代码并检查了发生了什么:

在第一种情况下 (运行ning without -s) shell 的 /proc/self/fd/0/dev/null 但 运行ning with -s shell 的 /proc/self/fd/0 在我的例子中是 /dev/pts/7

$ pgrep -f has-st
12727
$ ls -la  /proc/12727/fd/0 
lrwx------ 1 quoyn quoyn 64 Aug  4 00:13 /proc/12727/fd/0 -> /dev/pts/7

$ pgrep -f has-st
12793
$ ls -la  /proc/12793/fd/0 
lr-x------ 1 quoyn quoyn 64 Aug  4 00:17 /proc/12793/fd/0 -> /dev/null

这个 si 因为当你 运行 使用“-s”时 pytest 不捕获 终端并简单地通过它,这就是为什么它是 /dev/pts/7.如果在 pytest 退出后我在终端中 运行 ls -l /proc/self/fd/0 我确实得到:

lrwx------ 1 quoyn quoyn 64 Aug  4 00:29 /proc/self/fd/0 -> /dev/pts/7

你也可以尝试 不使用 pytest,只有 python:

import subprocess
s = subprocess.run(["ls", "-l", "/proc/self/fd/0"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, input=None, check=True, shell=False)
print(s.stdout)

并尝试以 python code_above.pyecho '' | python code_above.py

的形式执行

编辑:

并回答第二个问题

How to run this pytest successfully without -s?

自己使用伪终端。 运行 pty 中的子进程。\

编辑 2:

小试reproduce/demonstrate这道题,运行如下代码为pytest test.pypytest -s test.py

import sys
def test():
    assert sys.stdin.isatty()
$ pip install ptyprocess==0.6.0
# test.py

import unittest

from ptyprocess import PtyProcess


class TestCase(unittest.TestCase):

    def test(self):
        process = PtyProcess.spawn(
            ["sh", "./has-stdin.sh"]
        )
        process.wait()
        output = process.read().decode("utf-8")
        if process.exitstatus:
            raise Exception(output.strip("\r\n"))
        assert output.strip("\r\n") == 'no'

✅ 使用 -s:

成功运行
$ pytest test.py -s
platform linux -- Python 3.7.7, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/maikel/docker/library/postgresql
collected 1 item

test.py .

=========================== 1 passed in 0.60s ===========================

✅ 在没有 -s 的情况下成功运行:

$ pytest test.py
platform linux -- Python 3.7.7, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: /Users/maikel/docker/library/postgresql
collected 1 item

test.py .

=========================== 1 passed in 0.60s ===========================

注意事项

  • 它的运行速度比 subprocess 慢很多(本例中为 0.60 秒对 0.02 秒)
  • stdoutstderr没有区别

这张图解释了这种情况:

来源:https://github.com/pexpect/ptyprocess/blob/3931cd4/docs/index.rst