shell_spec.lua 5.7 KB

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