Не удается запустить подпроцесс, если не используется pytest no-capture - PullRequest
3 голосов
/ 03 августа 2020

Настройка

Я упростил задачу до сути:

  1. Установлен pytest
pip install pytest==5.4.3
Наличие сценария оболочки
# has-stdin.sh

# Detect stdin
if [[ ! -t 0 ]]; then
    echo "yes"
else
    echo "no"
fi
Пройдя 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 оболочке

$ 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? Как успешно запустить этот pytest без -s?

Я пробовал Тестовый вызов внешнего скрипта, запущенного через Popen, который не ожидает доступных данных в stdin с использованием pytest , но при его использовании метод обе команды pytest не работают.

Ответы [ 2 ]

2 голосов
/ 03 августа 2020

Это очень интересная проблема.

Я был так заинтригован, что я запустил код локально и изучил, что происходит:

В первом случае (выполняется without -s) оболочка /proc/self/fd/0 это /dev/null, но работает with -s /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. Если я запустил ls -l /proc/self/fd/0 в своем терминале после того, как pytest завершил работу, я действительно получаю:

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.py и echo '' | python code_above.py

EDIT:

И чтобы ответить на второй вопрос

Как успешно запустить этот pytest без -s?

Используйте псевдотерминал самостоятельно. Запустите подпроцесс в pty . \

EDIT2:

Минимальный тест для воспроизведения / демонстрации этой проблемы, запустите следующий код как pytest test.py и pytest -s test.py :

import sys
def test():
    assert sys.stdin.isatty()
0 голосов
/ 04 августа 2020
$ 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 с в этом примере)
  • Нет различия между stdout и stderr

Ситуация поясняется этой диаграммой:

pty-diagram

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

...