neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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)