【问题标题】:Python and Pytest calling a shell command containing tail failsPython 和 Pytest 调用包含 tail 的 shell 命令失败
【发布时间】:2020-06-29 11:42:24
【问题描述】:

环境: 平台 qnx——Python 2.7.12、pytest-4.6.9、py-1.8.1、pluggy-0.13.1 插件:json-report-1.2.1、shell-0.2.3

注意: 我知道 Python2.7 很旧且不受支持,但目前 QNX 没有其他版本可用。

问题:

我正在运行一个测试,当某个关键字出现在其日志中时,它应该终止服务。为此,我需要它在后台运行。 为此,我使用以下 shell 命令:

def test_kill_process():
    expected_output="XXXXXXXXX"
    expected_rc=0
    check_kill_process(expected_output, expected_rc)

import os
def check_kill_process(expected_output, expected_rc):
    test_log = File(r"/path/to/log")
    erase_log_entry = "Action"
    service=MyService()
    service.start()
    sleep(2)
    kill_command = "tail -f " + test_log.file_path + " | grep --line-buffered " + erase_log_entry + \
            " | while read ; do kill " + service.pid + " ; done &"
    os.popen(kill_command)
    service.action()
    f = open(test_log.file_path, "r")
    output = f.read()
    assert re.search(expected_output, output)

================================================ ==========================

没有 Pytest 甚至 Python,就像一个魅力。

如果我尝试使用 subprocess 模块运行命令,测试会无限期冻结。 如果我尝试使用 os.popen 或 os.system,命令以错误结束:

tail:  read failed in '/path/to/logfile' (Invalid argument)

此外,如果我尝试同样的事情,只有一只“猫”我会得到这个:

--stdout--: Broken pipe

如果有人有任何想法,请提前感谢!

【问题讨论】:

  • 如果我尝试使用子进程 - subprocess 在空环境下运行,因此LOGFILEKEYWORD 都不会被定义。使用 env 参数传递环境。
  • 对不起,我在这里写的不是很好。我直接传递参数,而不是作为 shell 变量。我重写了这个问题。感谢您的及时答复,但事实并非如此......
  • 请分享完整的源代码

标签: python pytest qnx


【解决方案1】:

根据经验,subprocess 和 popen 喜欢命令序列而不是字符串。我认为技术原因是因为不同的系统如何解释字符串路径与参数。查看popen documetation 以获得更深入的解释。

但是例如,如果我想执行命令git commit -m "fixes a bug.",我将不得不将该字符串命令拆分为一个以空格分隔的列表。

# command as a string
cmd = 'git commit -m "fixes a bug."'

# command as sequence of arguments. Consider using shlex.split() instead
cmd = cmd.split(' ')

# now call popen with the new formatted arguments
os.popen(cmd)

使您的脚本更加健壮。对于更复杂的情况,您可以使用内置函数 shlex.split() 将字符串拆分为类似 shell 的语法。

此外,您似乎也在命令中使用| 管道。在调用 popen 时,您必须通过 shell=True 才能使管道正常工作。

【讨论】:

  • 感谢您的意见。子进程总是使用列表,但不知道 shlex。但是,这仍然不能解决我的问题。使用 subprocess.Popen(shlex.split(kill_command)) 它只是挂起。
  • 看起来您也在命令中使用| 管道。在调用 popen 时,您必须通过 shell=True 才能使管道正常工作。根据这篇帖子stackoverflow.com/questions/13332268/…
  • 试过了,但结果相同。测试无限期挂起。
【解决方案2】:

不要使用subprocess(这通常是单一python的症状),为什么不直接使用open(logfile, 'r')并在循环中使用file.readline()来读取文件?这将与使用 tail 产生相同的效果,而无需子进程的开销(生成的代码也更清晰,更易于理解)。

这应该非常适合您尝试做的事情:

def wait_for_line(filename):
    f = open(filename, 'r')
    while 1:
        if line := f.readline():
            if re.match(some_regex, line):
                return True
        else:
            sleep(0.5)

这是一个完整的工作示例:

import threading
from time import sleep
import re

FILENAME = 'some_file'
REGEX = r'^foo\s...!\s\d+$'
REGEX_MATCH = "foo bar! 123"

def perform_other_action(filename):
    f = open(filename, 'w')
    sleep(5) # just for effect
    f.write(REGEX_MATCH)


def wait_for_line(filename):
    f = open(filename, 'r')
    while 1:
        if line := f.readline():
            if re.match(REGEX, line):
                return True
        else:
            sleep(0.5) # throttle the read frequency if no new lines are found

with open(FILENAME, 'w') as f: # make sure file exists
    pass

t = threading.Thread(target=wait_for_line, args=(FILENAME,))
t.start() # returns immediately, wait_for_line runs in background
perform_other_action(FILENAME)
t.join()

【讨论】:

  • 问题是我需要它在后台运行,所以我还可以向服务发出一个最终匹配 shell 行中条件的操作。
  • @RazvanBadea 你能在开始这个循环之前运行那个动作吗?如果这不可能,那么创建第二个线程来管理操作如何?
  • 我的第一选择是线程。我遇到了同样的问题 - 挂起。我什至制作了一个小测试脚本,它只用 python 就能很好地运行,但用 PyTest 就挂了。不过我会尝试颠倒顺序。
  • 你试过用原生的os.kill()方法替换子进程吗?将MyService() 设置放在测试夹具中也是一个好主意。
猜你喜欢
  • 1970-01-01
  • 2017-05-25
  • 1970-01-01
  • 1970-01-01
  • 2016-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-29
相关资源
最近更新 更多