【问题标题】:Running a subprocess fails when not using pytest no-capture不使用 pytest no-capture 时运行子进程失败
【发布时间】:2020-11-23 17:35:58
【问题描述】:

设置

我已将问题简化为核心:

  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?

我尝试过Test calling of an external script run via Popen that expects no available data in stdin using pytest,但是当使用该方法时,两个pytest 命令都失败了。

【问题讨论】:

    标签: python subprocess pytest


    【解决方案1】:

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

    我很感兴趣,所以我确实在本地运行了代码并检查了发生了什么:

    在第一种情况下(运行without -s),shell 的/proc/self/fd/0/dev/null,但运行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 执行

    编辑:

    然后回答第二个问题

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

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

    EDIT2:

    重现/演示此问题的最小测试,以pytest test.pypytest -s test.py 运行以下代码:

    import sys
    def test():
        assert sys.stdin.isatty()
    

    【讨论】:

    • 感谢您推动我的方向。 ? 我潜入pty 并找到了满足我需要的ptyprocess
    【解决方案2】:
    $ 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 秒 vs 0.02 秒)
    • stdoutstderr 之间没有区别

    下图说明情况:

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

    【讨论】:

      猜你喜欢
      • 2020-02-26
      • 2023-03-08
      • 1970-01-01
      • 2020-02-09
      • 2010-12-21
      • 2020-04-08
      • 1970-01-01
      • 2016-08-25
      • 1970-01-01
      相关资源
      最近更新 更多