Как запустить жгут Perl ``proof`` TAP в небуферизованном режиме?
Как часть набора тестов, написанного на Python 3 [.4-.6] для Linux, я должен выполнить ряд сторонних тестов. Сторонние тесты - это bash-скрипты. Они предназначены для работы с Perl prove
Жгут проводов. Один скрипт bash может содержать до нескольких тысяч отдельных тестов, и некоторые из них могут зависать бесконечно долго. После тайм-аута я хочу убить тестовый скрипт и собрать некоторую информацию о том, где он застрял.
Поскольку скрипты bash создают свои собственные процессы, я стараюсь изолировать весь prove
дерево процессов в новую группу процессов, поэтому я могу в конечном итоге убить всю группу процессов в целом, если что-то пойдет не так. Поскольку тесты должны выполняться с правами root, я использую sudo -b
для создания новой группы процессов с привилегиями root. Эта стратегия (в отличие от использования setsid
так или иначе) является результатом комментариев, которые я получил по этому вопросу в SE Unix&Linux
Проблема в том, что я теряю весь вывод из prove
Если использовать "преждевременно" при запуске с нажатием sudo -b
через питона subprocess.Popen
,
Я выделил это в простой контрольный пример. Ниже приведен скрипт тестирования bash с именем job.t
:
#!/bin/bash
MAXCOUNT=20
echo "1..$MAXCOUNT"
for (( i=1; i<=$MAXCOUNT; i++ ))
do
echo "ok $i"
sleep 1
done
Просто для сравнения, я также написал скрипт на Python с именем 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 job.py
все выходные данные этого сценария вплоть до момента его уничтожения фиксируются и печатаются успешно. когда run_demo
запускается во второй раз с тестовым скриптом demo bash job.t
на вершине prove
, выходные данные не записываются, и печатаются только пустые строки.
user@computer:~> 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:
user@computer:~>
Что здесь происходит и как я могу это исправить?
Т.е. как я могу прервать / прекратить выполнение сценария bash с помощью prove
(и вся его группа процессов) таким образом, чтобы я мог захватить его вывод?
РЕДАКТИРОВАТЬ: В ответ было предложено, что наблюдаемое поведение происходит из-за буферизации Perl его выходных данных. В отдельном скрипте Perl это можно отключить. Тем не менее, нет очевидного варианта, позволяющего отключить буферизацию для 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
, но я могу генерировать их сам.
2 ответа
По умолчанию STDOUT многих программ (в том числе perl
) буферизуется строкой (сбрасывается на новой строке), когда STDOUT подключен к терминалу, и буферизуется блоком (сбрасывается, когда буфер файла заполнен) в противном случае (например, когда он подключен к каналу).
Вы можете обмануть такие программы, используя буферизацию строки, используя псевдо-tty (ptty) вместо канала. С этой целью, unbuffer
твой друг. На Ubuntu это часть expect
пакет (sudo apt install expect
).
Из документов:
unbuffer отключает буферизацию вывода, которая происходит, когда вывод программы перенаправляется из неинтерактивных программ. Например, предположим, что вы просматриваете вывод с fifo, пропустив его через od, а затем more.
od -c /tmp/fifo | more
Вы ничего не увидите, пока не будет произведена полная страница вывода.
Вы можете отключить эту автоматическую буферизацию следующим образом:
unbuffer od -c /tmp/fifo | more
Я попробовал ваш пример кода и получил тот же результат, что вы описали (благодаря вашему Minimal, Complete и Verifiable пример!).
Я тогда поменял
run_demo(['sudo', '-b', 'prove', '-v', os.path.join(os.getcwd(), 'job.t')], timeout_sec, signal.SIGINT)
в
run_demo(['sudo', '-b', 'unbuffer', 'prove', '-v', os.path.join(os.getcwd(), 'job.t')], timeout_sec, signal.SIGINT)
То есть: я просто добавил unbuffer
к prove
команда. Выход был тогда:
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 8, in <module>
time.sleep(1)
KeyboardInterrupt
DEMO: sudo -b unbuffer prove -v /home/dirk/w/sam/p/job.t
KILLED!
Got our/err: /home/dirk/w/sam/p/job.t ..
1..20
ok 1
ok 2
ok 3
ok 4
ok 5
Это начало ответа, в нем больше информации, чем я могу втиснуть в комментарий.
Выложенная вами проблема на самом деле не связана с bash, она связана с Perl. В моей системе which prove
указывает на /usr/bin/prove
, который является сценарием Perl. Реальный вопрос здесь, как правило, о сценариях Perl, даже не относится к prove
, Я скопировал ваши файлы выше и проверил, что могу воспроизвести то, что вы видите, затем я создал третий тест:
$ cat job.pl
#!/usr/bin/perl
foreach (1..20){
print "$_\n";
sleep 1;
}
Круто, тогда я добавил это в демонстрационную программу:
(После импорта shlex
как хорошо`):
cmdargs = shlex.split('sudo -b '+os.path.join(os.getcwd(), 'job.pl'))
run_demo(cmdargs, timeout_sec, signal.SIGINT)
И, конечно же, этот простой Perl-скрипт не может выдавать результат при уничтожении.
$ python3 demo.py
...(output as you wrote above followed by)...
DEMO: sudo -b /home/jawguychooser/job.pl
KILLED!
Got our/err:
$
Таким образом, это означает, что ваша проблема на самом деле является конкретным примером того, как захватить вывод из убитой perl-программы, работающей в фоновом режиме, управляемой Python-программой.
В качестве следующего шага я установил job.pl
снять буфер с stdout:
$ cat job.pl
#!/usr/bin/perl
$| = 1;
foreach (1..20){
print "$_\n";
sleep 1;
}
А потом я снова запускаю demo.py и вуаля!
$ python3 demo.py
DEMO: sudo -b /home/jawguychooser/job.pl
KILLED!
Got our/err: 1
2
3
4
5
6
$
Так что, возможно, если вы взломаете сценарий доказательства и настроите его для работы без буферизации, это будет делать то, что вы хотите. В любом случае, я думаю, что ваш вопрос сейчас "как я могу бежать prove -v
в небуферизованном режиме ".
Надеюсь, это поможет.