如何在无缓冲模式下运行Perl的``prove`` TAP线束?

时间:2017-12-26 19:57:10

标签: python linux perl tap perl-prove

作为Linux上用Python 3 [。4-.6]编写的测试套件的一部分,我必须运行一些第三方测试。第三方测试是bash脚本。它们旨在与Perl's prove TAP harness一起运行。一个bash脚本最多可以包含几千个单独的测试 - 其中一些可以无限期挂起。超时后,我想杀死测试脚本并收集有关它被卡住的地方的一些信息。

因为bash脚本创建了自己的进程,所以我尝试将整个prove进程树隔离到一个新进程组中,因此如果出现问题,我最终可能会整个进程组被杀死。由于测试必须以root权限运行,因此我使用sudo -b创建具有root权限的新进程组。这种策略(与以某种方式使用setsid相反)是我在this question at SE Unix&Linux上收到的评论的结果

问题在于,如果我过早地使用prove TAP线束,我会丢失所有输出。'过早地'通过Python sudo -b使用subprocess.Popen启动时。

我把它分成了一个简单的测试用例。以下是名为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测试基础架构&#34;名为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权限的新进程组。两次,一个&#34;测试工作&#34;每秒打印一行(ok [line number]) - 理论上为20秒/ 20行。但是,两个脚本都会超时5秒,并且在此超时后整个进程组将被终止。

run_demo第一次使用我的小Python脚本job.py运行时,该脚本的所有输出一直到被杀死的点被捕获并成功打印。当run_demojob.t之上使用演示bash测试脚本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:~>

这里发生了什么,我该如何解决?

即。如何以可以捕获其输出的方式中断/终止与prove(及其整个进程组)一起运行的bash测试脚本?

编辑:suggested in an answer观察到的行为是因为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 个答案:

答案 0 :(得分:5)

默认情况下,当STDOUT连接到终端时,许多程序(包括perl)的STDOUT都是行缓冲的(刷新在换行符上),并且是块缓冲的(当文件被刷新时刷新)缓冲区已满)否则(例如,当它连接到管道时)。

你可以通过使用伪tty(ptty)而不是管道来欺骗这些程序使用行缓冲。为此, unbuffer 是您的朋友。在Ubuntu上,这是expect包(sudo apt install expect)的一部分。

来自docs

  

unbuffer 禁用输出缓冲   重定向程序输出时发生的情况   来自非互动节目。例如,   假设您正在观看a的输出   通过运行od然后更多来运行它。

od -c /tmp/fifo | more
     

直到整页才会看到任何内容   已经产生了产出。

     

您可以按如下方式禁用此自动缓冲:

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

答案 1 :(得分:3)

这是一个答案的开头,它有更多的信息,而不是我可以挤进评论。

您提出的问题与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程序的输出的特定实例。

下一步,我将job.pl设置为unbuffer 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
$ 

所以,也许如果你破解了验证脚本并设置了运行无缓冲的那些可以做你想要的东西。无论如何,我认为你现在的问题是&#34;如何在无缓冲模式下运行prove -v&#34;。

我希望这会有所帮助。