Как часть тестового набора, написанного на Python 3 [. 4-.6] в Linux, мне нужно запустить ряд сторонних тестов. Тесты сторонних разработчиков - это bash скрипты. Они предназначены для работы с Perl prove
TAP harness. Один bash script может содержать до нескольких тысяч отдельных тестов - и некоторые из них могут зависать бесконечно. После таймаута я хочу убить тест script и собрать некоторую информацию о том, где он застрял.
Поскольку сценарии bash создают собственные процессы, я пытаюсь изолировать все дерево процессов prove
в новой группе процессов, поэтому я могу в конечном итоге убить всю группу процессов в целом, если все пойдет не так. Поскольку тесты должны выполняться с привилегиями root, я использую sudo -b
для создания новой группы процессов с привилегиями root. Эта стратегия (в отличие от использования setsid
в той или иной форме) является результатом комментариев, полученных мной по этому вопросу в SE Unix & Linux
Проблема в том, что я теряю весь вывод из жгута prove
TAP, если я его преждевременно убиваю, когда запускается с помощью sudo -b
через Python subprocess.Popen
.
Я выделил его в простой тестовый пример. Ниже приведен тест bash script с именем job.t
:
#!/bin/bash
MAXCOUNT=20
echo "1..$MAXCOUNT"
for (( i=1; i<=$MAXCOUNT; i++ ))
do
echo "ok $i"
sleep 1
done
Просто для сравнения, я также написал Python script с именем job.py
, производя более или менее тот же вывод и проявляя такое же поведение:
import sys
import time
if __name__ == '__main__':
maxcount = 20
print('1..%d' % maxcount)
for i in range(1, maxcount + 1):
sys.stdout.write('ok %d\n' % i)
time.sleep(1)
И последнее, но не менее важное: следующая моя "тестовая инфраструктура Python" с именем demo.py
:
import psutil # get it with "pip install psutil"
import os
import signal
import subprocess
def run_demo(cmd, timeout_after_seconds, signal_code):
print('DEMO: %s' % ' '.join(cmd))
proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
try:
outs, errs = proc.communicate(timeout = timeout_after_seconds)
except subprocess.TimeoutExpired:
print('KILLED!')
kill_pid = _get_pid(cmd)
subprocess.Popen(['sudo', 'kill', '-%d' % signal_code, '--', '-%d' % os.getpgid(kill_pid)]).wait()
outs, errs = proc.communicate()
print('Got our/err:', outs.decode('utf-8'), errs.decode('utf-8'))
def _get_pid(cmd_line_list):
for pid in psutil.pids():
proc = psutil.Process(pid)
if cmd_line_list == proc.cmdline():
return proc.pid
raise # TODO some error ...
if __name__ == '__main__':
timeout_sec = 5
# Works, output is captured and eventually printed
run_demo(['sudo', '-b', 'python', 'job.py'], timeout_sec, signal.SIGINT)
# Failes, output is NOT captured (i.e. printed) and therefore lost
run_demo(['sudo', '-b', 'prove', '-v', os.path.join(os.getcwd(), 'job.t')], timeout_sec, signal.SIGINT)
При запуске demo.py
он запускает процедуру run_demo
дважды - с различными конфигурациями. Оба раза начинается новая группа процессов с правами root. Оба раза "тестовое задание" печатает новую строку (ok [line number]
) один раз в секунду - теоретически в течение 20 секунд /20 строк. Тем не менее, время ожидания составляет 5 секунд для обоих сценариев, и вся эта группа процессов убивается после этого таймаута.
Когда run_demo
запускается в первый раз с моим маленьким Python script job.py
, весь вывод этого script полностью до точки, когда он был убит, захватывается и печатается успешно. Когда run_demo
выполняется во второй раз с демонстрационным bash тестом script job.t
поверх prove
, вывод не записывается и печатаются только пустые строки.
[email protected]:~> python demo.py
DEMO: sudo -b python job.py
KILLED!
Got our/err: 1..20
ok 1
ok 2
ok 3
ok 4
ok 5
ok 6
Traceback (most recent call last):
File "job.py", line 11, in <module>
time.sleep(1)
KeyboardInterrupt
DEMO: sudo -b prove -v /full/path/to/job.t
KILLED!
Got our/err:
[email protected]:~>
Что здесь происходит и как я могу это исправить?
т.е. как я могу прерывать/завершать тест bash script, работающий с prove
(и всей его группой процессов) таким образом, чтобы я мог записать его вывод?
EDIT: предложил в ответ, что наблюдаемое поведение происходит из-за того, что Perl выполняет буферизацию своего вывода. В пределах индивидуального Perl script это можно отключить. Однако нет очевидной опции, позволяющей отключить буферизацию для prove
[-v]. Как я могу достичь этого?
Я могу обойти эту проблему, выполнив свое тестовое задание с помощью bash
напрямую. Следующая команда должна быть изменена с
run_demo(['sudo', '-b', 'prove', '-v', os.path.join(os.getcwd(), 'job.t')], timeout_sec, signal.SIGINT)
к
run_demo(['sudo', '-b', 'bash', os.path.join(os.getcwd(), 'job.t')], timeout_sec, signal.SIGINT)
Таким образом, я не получаю тестовую статистику, напечатанную prove
, но я могу сгенерировать их самостоятельно.