Bash补全脚本的单元测试

16 投票
2 回答
2429 浏览
提问于 2025-04-17 12:18

我想为一个比较复杂的Bash自动补全脚本写一个单元测试,最好是用Python来实现。我的目标是通过编程的方式获取Bash自动补全的值。
这个测试应该像这样:

def test_completion():
  # trigger_completion should return what a user should get on triggering 
  # Bash completion like this: 'pbt createkvm<TAB>' 
  assert trigger_completion('pbt createkvm') == "module1 module2 module3" 

我该如何通过编程模拟Bash的自动补全,以便在我的工具的测试套件中检查补全的值呢?

2 个回答

1

bonsaiviking的解决方案对我来说差不多可行。我需要修改一下bash脚本字符串。我在执行的bash脚本中添加了一个额外的';'分隔符,否则在Mac OS X上执行就不行了。具体原因我也不太清楚。

我还稍微调整了一下各种COMP_参数的初始化,以便处理我遇到的不同情况。

最终的解决方案是一个辅助类,用来从python测试bash补全功能,这样上面的测试就可以写成:

from completion import BashCompletionTest

class AdsfTestCase(BashCompletionTest):
    def test_orig(self):
        self.run_complete("other arguments f", "four five")

    def run_complete(self, command, expected):
        completion_file="adsf-completion"
        program="asdf"
        super(AdsfTestCase, self).run_complete(completion_file, program, command, expected)


if (__name__=='__main__'):
    unittest.main()

补全库位于 https://github.com/lacostej/unity3d-bash-completion/blob/master/lib/completion.py

9

假设你有一个叫做 asdf-completion 的文件,这个文件里有一个 bash 自动补全的脚本,内容如下:

_asdf() {
COMPREPLY=()
local cur prev
cur=$(_get_cword)
COMPREPLY=( $( compgen -W "one two three four five six" -- "$cur") )
return 0
}    
complete -F _asdf asdf

这个脚本使用了一个叫 _asdf 的函数,来为一个虚构的 asdf 命令提供自动补全的功能。如果我们设置好正确的环境变量(可以参考 bash 手册),那么就能得到相同的效果,也就是把可能的补全结果放到 COMPREPLY 这个变量里。下面是一个在单元测试中实现这个功能的例子:

import subprocess
import unittest

class BashTestCase(unittest.TestCase):
    def test_complete(self):
        completion_file="asdf-completion"
        partial_word="f"
        cmd=["asdf", "other", "arguments", partial_word]
        cmdline = ' '.join(cmd)

        out = subprocess.Popen(['bash', '-i', '-c',
            r'source {compfile}; COMP_LINE="{cmdline}" COMP_WORDS=({cmdline}) COMP_CWORD={cword} COMP_POINT={cmdlen} $(complete -p {cmd} | sed "s/.*-F \\([^ ]*\\) .*/\\1/") && echo ${{COMPREPLY[*]}}'.format(
                compfile=completion_file, cmdline=cmdline, cmdlen=len(cmdline), cmd=cmd[0], cword=cmd.index(partial_word)
                )],
            stdout=subprocess.PIPE)
        stdout, stderr = out.communicate()
        self.assertEqual(stdout, "four five\n")

if (__name__=='__main__'):
    unittest.main()

这个方法应该适用于任何使用 -F 的补全,但也可能适用于其他类型的补全。

je4d 提到的使用 expect 的建议是个不错的主意,可以让测试更加全面。

撰写回答