我如何 运行 Perl 的 ``prove`` TAP harness 在无缓冲模式下?
How do I run Perl's ``prove`` TAP harness in unbuffered mode?
作为 Python 3[.4-.6] 中 Linux 编写的测试套件的一部分,我必须 运行 一些第 3 方测试。第 3 方测试是 bash 脚本。它们旨在 运行 和 Perl's prove
TAP harness。一个 bash 脚本可以包含多达数千个单独的测试 - 其中一些可以无限期挂起。超时后,我想终止测试脚本并收集有关卡住位置的一些信息。
因为 bash 脚本创建了自己的进程,我尝试将整个 prove
进程树隔离到一个新的进程组中,所以我最终可以将整个进程组作为一个整体杀死如果出现问题。因为测试必须 运行 具有 root 权限,所以我使用 sudo -b
创建一个具有 root 权限的新进程组。这种策略(与以一种或另一种方式使用 setsid
相反)是我在 this question at SE Unix&Linux
上收到的评论的结果
问题是,如果我在 sudo -b
到 Python 的 [=23] 启动时杀死它 'prematurely',我会丢失 prove
TAP 线束的所有输出=].
我将它隔离成一个简单的测试用例。下面是一个名为job.t
的bash测试脚本:
#!/bin/bash
MAXCOUNT=20
echo "1..$MAXCOUNT"
for (( i=1; i<=$MAXCOUNT; i++ ))
do
echo "ok $i"
sleep 1
done
为了比较,我还编写了一个名为 job.py
的 Python 脚本,它产生或多或少相同的输出并表现出相同的行为:
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 test infrastructure",名为 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 权限的新进程组。两次,"test job" 每秒打印一次新行 (ok [line number]
) - 理论上为 20 秒/20 行。但是,这两个脚本都有 5 秒的超时时间,并且在该超时时间后整个进程组将被终止。
当 run_demo
运行 第一次使用我的小 Python 脚本 job.py
时,该脚本的所有输出一直到它被杀死被捕获并成功打印。当 run_demo
运行s 第二次使用演示 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
打印的测试统计数据,但我可以自己生成它们。
这是一个答案的开始,它包含的信息比我能挤进评论的还要多。
您提出的问题实际上与 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:
$
所以,这意味着您的问题实际上是一个特定实例,说明如何在 Python 程序控制的后台捕获已终止的 perl 程序 运行ning 的输出。
作为下一步,我将 job.pl
设置为取消缓冲标准输出:
$ 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
$
因此,也许如果您黑入证明脚本并将事情设置为 运行 无缓冲,那将执行您想要的操作。无论如何,我认为你现在的问题是 "how can I run prove -v
in unbuffered mode".
希望对您有所帮助。
默认情况下,当 STDOUT 连接到终端时,许多程序(包括 perl
)的 STDOUT 是行缓冲的(在换行符上刷新),并且是块缓冲的(当文件缓冲区被刷新时刷新)满)否则(例如,当它连接到管道时)。
您可以通过使用伪 tty (ptty) 而不是管道来欺骗此类程序使用行缓冲。为此,unbuffer
是您的朋友。在 Ubuntu 上,这是 expect
软件包 (sudo apt install expect
) 的一部分。
来自docs:
unbuffer disables the output buffering
that occurs when program output is redirected
from non-interactive programs. For example,
suppose you are watching the output from a
fifo by running it through od and then more.
od -c /tmp/fifo | more
You will not see anything until a full page
of output has been produced.
You can disable this automatic buffering as follows:
unbuffer od -c /tmp/fifo | more
我尝试了您的示例代码并得到了与您描述的相同的结果(感谢您的 Minimal, Complete, and Verifiable example!)。
然后我改了
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
作为 Python 3[.4-.6] 中 Linux 编写的测试套件的一部分,我必须 运行 一些第 3 方测试。第 3 方测试是 bash 脚本。它们旨在 运行 和 Perl's prove
TAP harness。一个 bash 脚本可以包含多达数千个单独的测试 - 其中一些可以无限期挂起。超时后,我想终止测试脚本并收集有关卡住位置的一些信息。
因为 bash 脚本创建了自己的进程,我尝试将整个 prove
进程树隔离到一个新的进程组中,所以我最终可以将整个进程组作为一个整体杀死如果出现问题。因为测试必须 运行 具有 root 权限,所以我使用 sudo -b
创建一个具有 root 权限的新进程组。这种策略(与以一种或另一种方式使用 setsid
相反)是我在 this question at SE Unix&Linux
问题是,如果我在 sudo -b
到 Python 的 [=23] 启动时杀死它 'prematurely',我会丢失 prove
TAP 线束的所有输出=].
我将它隔离成一个简单的测试用例。下面是一个名为job.t
的bash测试脚本:
#!/bin/bash
MAXCOUNT=20
echo "1..$MAXCOUNT"
for (( i=1; i<=$MAXCOUNT; i++ ))
do
echo "ok $i"
sleep 1
done
为了比较,我还编写了一个名为 job.py
的 Python 脚本,它产生或多或少相同的输出并表现出相同的行为:
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 test infrastructure",名为 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 权限的新进程组。两次,"test job" 每秒打印一次新行 (ok [line number]
) - 理论上为 20 秒/20 行。但是,这两个脚本都有 5 秒的超时时间,并且在该超时时间后整个进程组将被终止。
当 run_demo
运行 第一次使用我的小 Python 脚本 job.py
时,该脚本的所有输出一直到它被杀死被捕获并成功打印。当 run_demo
运行s 第二次使用演示 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
(及其整个进程组),以便我可以捕获其输出?
编辑: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
打印的测试统计数据,但我可以自己生成它们。
这是一个答案的开始,它包含的信息比我能挤进评论的还要多。
您提出的问题实际上与 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:
$
所以,这意味着您的问题实际上是一个特定实例,说明如何在 Python 程序控制的后台捕获已终止的 perl 程序 运行ning 的输出。
作为下一步,我将 job.pl
设置为取消缓冲标准输出:
$ 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
$
因此,也许如果您黑入证明脚本并将事情设置为 运行 无缓冲,那将执行您想要的操作。无论如何,我认为你现在的问题是 "how can I run prove -v
in unbuffered mode".
希望对您有所帮助。
默认情况下,当 STDOUT 连接到终端时,许多程序(包括 perl
)的 STDOUT 是行缓冲的(在换行符上刷新),并且是块缓冲的(当文件缓冲区被刷新时刷新)满)否则(例如,当它连接到管道时)。
您可以通过使用伪 tty (ptty) 而不是管道来欺骗此类程序使用行缓冲。为此,unbuffer
是您的朋友。在 Ubuntu 上,这是 expect
软件包 (sudo apt install expect
) 的一部分。
来自docs:
unbuffer disables the output buffering that occurs when program output is redirected from non-interactive programs. For example, suppose you are watching the output from a fifo by running it through od and then more.
od -c /tmp/fifo | more
You will not see anything until a full page of output has been produced.
You can disable this automatic buffering as follows:
unbuffer od -c /tmp/fifo | more
我尝试了您的示例代码并得到了与您描述的相同的结果(感谢您的 Minimal, Complete, and Verifiable example!)。
然后我改了
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