shell_spec.lua 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. local helpers = require('test.unit.helpers')(after_each)
  2. local itp = helpers.gen_itp(it)
  3. local cimported = helpers.cimport(
  4. './src/nvim/os/shell.h',
  5. './src/nvim/option_defs.h',
  6. './src/nvim/main.h',
  7. './src/nvim/memory.h'
  8. )
  9. local ffi, eq = helpers.ffi, helpers.eq
  10. local intern = helpers.internalize
  11. local to_cstr = helpers.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(
  23. cmd and to_cstr(cmd),
  24. 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. local function shell_argv_to_str(argv_table)
  39. -- C string array (char **).
  40. local argv = (argv_table
  41. and ffi.new("char*[?]", #argv_table+1)
  42. or NULL)
  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. local res = cimported.shell_argv_to_str(argv)
  53. return ffi.string(res)
  54. end
  55. local function os_system(cmd, input)
  56. local input_or = input and to_cstr(input) or NULL
  57. local input_len = (input ~= nil) and string.len(input) or 0
  58. local output = ffi.new('char *[1]')
  59. local nread = ffi.new('size_t[1]')
  60. local argv = ffi.cast('char**',
  61. cimported.shell_build_argv(to_cstr(cmd), nil))
  62. local status = cimported.os_system(argv, input_or, input_len, output, nread)
  63. return status, intern(output[0], nread[0])
  64. end
  65. describe('os_system', function()
  66. itp('can echo some output (shell builtin)', function()
  67. local cmd, text = 'printf "%s "', 'some text '
  68. local status, output = os_system(cmd .. ' ' .. text)
  69. eq(text, output)
  70. eq(0, status)
  71. end)
  72. itp('can deal with empty output', function()
  73. local cmd = 'printf ""'
  74. local status, output = os_system(cmd)
  75. eq('', output)
  76. eq(0, status)
  77. end)
  78. itp('can pass input on stdin', function()
  79. local cmd, input = 'cat -', 'some text\nsome other text'
  80. local status, output = os_system(cmd, input)
  81. eq(input, output)
  82. eq(0, status)
  83. end)
  84. itp('returns non-zero exit code', function()
  85. local status = os_system('exit 2')
  86. eq(2, status)
  87. end)
  88. end)
  89. describe('shell_build_argv', function()
  90. itp('works with NULL arguments', function()
  91. eq({'/bin/sh'}, shell_build_argv(nil, nil))
  92. end)
  93. itp('works with cmd', function()
  94. eq({'/bin/sh', '-c', 'abc def'}, shell_build_argv('abc def', nil))
  95. end)
  96. itp('works with extra_args', function()
  97. eq({'/bin/sh', 'ghi jkl'}, shell_build_argv(nil, 'ghi jkl'))
  98. end)
  99. itp('works with cmd and extra_args', function()
  100. eq({'/bin/sh', 'ghi jkl', '-c', 'abc def'}, shell_build_argv('abc def', 'ghi jkl'))
  101. end)
  102. itp('splits and unquotes &shell and &shellcmdflag', function()
  103. cimported.p_sh = to_cstr('/Program" "Files/zsh -f')
  104. cimported.p_shcf = to_cstr('-x -o "sh word split" "-"c')
  105. eq({'/Program Files/zsh', '-f',
  106. 'ghi jkl',
  107. '-x', '-o', 'sh word split',
  108. '-c', 'abc def'},
  109. shell_build_argv('abc def', 'ghi jkl'))
  110. end)
  111. itp('applies shellxescape (p_sxe) and shellxquote (p_sxq)', function()
  112. cimported.p_sxq = to_cstr('(')
  113. cimported.p_sxe = to_cstr('"&|<>()@^')
  114. local argv = ffi.cast('char**',
  115. cimported.shell_build_argv(to_cstr('echo &|<>()@^'), nil))
  116. eq(ffi.string(argv[0]), '/bin/sh')
  117. eq(ffi.string(argv[1]), '-c')
  118. eq(ffi.string(argv[2]), '(echo ^&^|^<^>^(^)^@^^)')
  119. eq(nil, argv[3])
  120. end)
  121. itp('applies shellxquote="(', function()
  122. cimported.p_sxq = to_cstr('"(')
  123. cimported.p_sxe = to_cstr('"&|<>()@^')
  124. local argv = ffi.cast('char**', cimported.shell_build_argv(
  125. to_cstr('echo -n some text'), nil))
  126. eq(ffi.string(argv[0]), '/bin/sh')
  127. eq(ffi.string(argv[1]), '-c')
  128. eq(ffi.string(argv[2]), '"(echo -n some text)"')
  129. eq(nil, argv[3])
  130. end)
  131. itp('applies shellxquote="', function()
  132. cimported.p_sxq = to_cstr('"')
  133. cimported.p_sxe = to_cstr('')
  134. local argv = ffi.cast('char**', cimported.shell_build_argv(
  135. to_cstr('echo -n some text'), nil))
  136. eq(ffi.string(argv[0]), '/bin/sh')
  137. eq(ffi.string(argv[1]), '-c')
  138. eq(ffi.string(argv[2]), '"echo -n some text"')
  139. eq(nil, argv[3])
  140. end)
  141. itp('with empty shellxquote/shellxescape', function()
  142. local argv = ffi.cast('char**', cimported.shell_build_argv(
  143. to_cstr('echo -n some text'), nil))
  144. eq(ffi.string(argv[0]), '/bin/sh')
  145. eq(ffi.string(argv[1]), '-c')
  146. eq(ffi.string(argv[2]), 'echo -n some text')
  147. eq(nil, argv[3])
  148. end)
  149. end)
  150. itp('shell_argv_to_str', function()
  151. eq('', shell_argv_to_str({ nil }))
  152. eq("''", shell_argv_to_str({ '' }))
  153. eq("'foo' '' 'bar'", shell_argv_to_str({ 'foo', '', 'bar' }))
  154. eq("'/bin/sh' '-c' 'abc def'", shell_argv_to_str({'/bin/sh', '-c', 'abc def'}))
  155. eq("'abc def' 'ghi jkl'", shell_argv_to_str({'abc def', 'ghi jkl'}))
  156. eq("'/bin/sh' '-c' 'abc def' '"..('x'):rep(225).."...",
  157. shell_argv_to_str({'/bin/sh', '-c', 'abc def', ('x'):rep(999)}))
  158. end)
  159. end)