shell_spec.lua (5856B)
1 local t = require('test.unit.testutil') 2 local itp = t.gen_itp(it) 3 local cimported = t.cimport( 4 './src/nvim/os/shell.h', 5 './src/nvim/option_vars.h', 6 './src/nvim/main.h', 7 './src/nvim/memory.h' 8 ) 9 local ffi, eq = t.ffi, t.eq 10 local intern = t.internalize 11 local to_cstr = t.to_cstr 12 local NULL = ffi.cast('void *', 0) 13 14 describe('shell functions', function() 15 before_each(function() 16 -- os_system() can't work when the p_sh and p_shcf variables are unset 17 cimported.p_sh = to_cstr('/bin/sh') 18 cimported.p_shcf = to_cstr('-c') 19 cimported.p_sxq = to_cstr('') 20 cimported.p_sxe = to_cstr('') 21 end) 22 23 local function shell_build_argv(cmd, extra_args) 24 local res = cimported.shell_build_argv(cmd and to_cstr(cmd), extra_args and to_cstr(extra_args)) 25 -- `res` is zero-indexed (C pointer, not Lua table)! 26 local argc = 0 27 local ret = {} 28 -- Explicitly free everything, so if it is not in allocated memory it will 29 -- crash. 30 while res[argc] ~= nil do 31 ret[#ret + 1] = ffi.string(res[argc]) 32 cimported.xfree(res[argc]) 33 argc = argc + 1 34 end 35 cimported.xfree(res) 36 return ret 37 end 38 39 local function shell_argv_to_str(argv_table) 40 -- C string array (char **). 41 local argv = (argv_table and ffi.new('char*[?]', #argv_table + 1) or NULL) 42 43 local argc = 1 44 while argv_table ~= nil and argv_table[argc] ~= nil do 45 -- `argv` is zero-indexed (C pointer, not Lua table)! 46 argv[argc - 1] = to_cstr(argv_table[argc]) 47 argc = argc + 1 48 end 49 if argv_table ~= nil then 50 argv[argc - 1] = NULL 51 end 52 53 local res = cimported.shell_argv_to_str(argv) 54 return ffi.string(res) 55 end 56 57 local function os_system(cmd, input) 58 local input_or = input and to_cstr(input) or NULL 59 local input_len = (input ~= nil) and string.len(input) or 0 60 local output = ffi.new('char *[1]') 61 local nread = ffi.new('size_t[1]') 62 63 local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr(cmd), nil)) 64 local status = cimported.os_system(argv, input_or, input_len, output, nread) 65 66 return status, intern(output[0], nread[0]) 67 end 68 69 describe('os_system', function() 70 itp('can echo some output (shell builtin)', function() 71 local cmd, text = 'printf "%s "', 'some text ' 72 local status, output = os_system(cmd .. ' ' .. text) 73 eq(text, output) 74 eq(0, status) 75 end) 76 77 itp('can deal with empty output', function() 78 local cmd = 'printf ""' 79 local status, output = os_system(cmd) 80 eq('', output) 81 eq(0, status) 82 end) 83 84 itp('can pass input on stdin', function() 85 local cmd, input = 'cat -', 'some text\nsome other text' 86 local status, output = os_system(cmd, input) 87 eq(input, output) 88 eq(0, status) 89 end) 90 91 itp('returns non-zero exit code', function() 92 local status = os_system('exit 2') 93 eq(2, status) 94 end) 95 end) 96 97 describe('shell_build_argv', function() 98 itp('works with NULL arguments', function() 99 eq({ '/bin/sh' }, shell_build_argv(nil, nil)) 100 end) 101 102 itp('works with cmd', function() 103 eq({ '/bin/sh', '-c', 'abc def' }, shell_build_argv('abc def', nil)) 104 end) 105 106 itp('works with extra_args', function() 107 eq({ '/bin/sh', 'ghi jkl' }, shell_build_argv(nil, 'ghi jkl')) 108 end) 109 110 itp('works with cmd and extra_args', function() 111 eq({ '/bin/sh', 'ghi jkl', '-c', 'abc def' }, shell_build_argv('abc def', 'ghi jkl')) 112 end) 113 114 itp('splits and unquotes &shell and &shellcmdflag', function() 115 cimported.p_sh = to_cstr('/Program" "Files/zsh -f') 116 cimported.p_shcf = to_cstr('-x -o "sh word split" "-"c') 117 eq( 118 { '/Program Files/zsh', '-f', 'ghi jkl', '-x', '-o', 'sh word split', '-c', 'abc def' }, 119 shell_build_argv('abc def', 'ghi jkl') 120 ) 121 end) 122 123 itp('applies shellxescape (p_sxe) and shellxquote (p_sxq)', function() 124 cimported.p_sxq = to_cstr('(') 125 cimported.p_sxe = to_cstr('"&|<>()@^') 126 127 local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo &|<>()@^'), nil)) 128 eq('/bin/sh', ffi.string(argv[0])) 129 eq('-c', ffi.string(argv[1])) 130 eq('(echo ^&^|^<^>^(^)^@^^)', ffi.string(argv[2])) 131 eq(nil, argv[3]) 132 end) 133 134 itp('applies shellxquote="(', function() 135 cimported.p_sxq = to_cstr('"(') 136 cimported.p_sxe = to_cstr('"&|<>()@^') 137 138 local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo -n some text'), nil)) 139 eq('/bin/sh', ffi.string(argv[0])) 140 eq('-c', ffi.string(argv[1])) 141 eq('"(echo -n some text)"', ffi.string(argv[2])) 142 eq(nil, argv[3]) 143 end) 144 145 itp('applies shellxquote="', function() 146 cimported.p_sxq = to_cstr('"') 147 cimported.p_sxe = to_cstr('') 148 149 local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo -n some text'), nil)) 150 eq('/bin/sh', ffi.string(argv[0])) 151 eq('-c', ffi.string(argv[1])) 152 eq('"echo -n some text"', ffi.string(argv[2])) 153 eq(nil, argv[3]) 154 end) 155 156 itp('with empty shellxquote/shellxescape', function() 157 local argv = ffi.cast('char**', cimported.shell_build_argv(to_cstr('echo -n some text'), nil)) 158 eq('/bin/sh', ffi.string(argv[0])) 159 eq('-c', ffi.string(argv[1])) 160 eq('echo -n some text', ffi.string(argv[2])) 161 eq(nil, argv[3]) 162 end) 163 end) 164 165 itp('shell_argv_to_str', function() 166 eq('', shell_argv_to_str({ nil })) 167 eq("''", shell_argv_to_str({ '' })) 168 eq("'foo' '' 'bar'", shell_argv_to_str({ 'foo', '', 'bar' })) 169 eq("'/bin/sh' '-c' 'abc def'", shell_argv_to_str({ '/bin/sh', '-c', 'abc def' })) 170 eq("'abc def' 'ghi jkl'", shell_argv_to_str({ 'abc def', 'ghi jkl' })) 171 eq( 172 "'/bin/sh' '-c' 'abc def' '" .. ('x'):rep(225) .. '...', 173 shell_argv_to_str({ '/bin/sh', '-c', 'abc def', ('x'):rep(999) }) 174 ) 175 end) 176 end)