summaryrefslogtreecommitdiffstats
path: root/src/testdir/test_shell.vim
blob: 7d91dff358133ae48e059fbb3cd26d36790f28cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
" Test for the shell related options ('shell', 'shellcmdflag', 'shellpipe',
" 'shellquote', 'shellredir', 'shellxescape', and 'shellxquote')

source check.vim
source shared.vim

func Test_shell_options()
  if has('win32')
    " FIXME: This test is flaky on MS-Windows.
    let g:test_is_flaky = 1
  endif

  " The expected value of 'shellcmdflag', 'shellpipe', 'shellquote',
  " 'shellredir', 'shellxescape', 'shellxquote' for the supported shells.
  let shells = []
  if has('unix')
    let shells += [['sh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['ksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['mksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['zsh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['zsh-beta', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['bash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['fish', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['ash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['dash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
          \ ['csh', '-c', '|& tee', '', '>&', '', ''],
          \ ['tcsh', '-c', '|& tee', '', '>&', '', ''],
          \ ['pwsh', '-c', '>%s 2>&1', '', '>%s 2>&1', '', '']]
  endif
  if has('win32')
    let shells += [['cmd', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', ''],
          \ ['cmd.exe', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '('],
          \ ['powershell.exe', '-Command', '2>&1 | Out-File -Encoding default',
          \           '', '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
          \ ['powershell', '-Command', '2>&1 | Out-File -Encoding default', '',
          \               '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
          \ ['pwsh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['pwsh', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['sh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['ksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['mksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['pdksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['zsh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['zsh-beta.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['bash.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['dash.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
          \ ['csh.exe', '-c', '>&', '', '>&', '"&|<>()@^', '"'],
          \ ['tcsh.exe', '-c', '>&', '', '>&', '"&|<>()@^', '"']]
  endif

  " start a new Vim instance with 'shell' set to each of the supported shells
  " and check the default shell option settings
  let after =<< trim END
    let l = [&shell, &shellcmdflag, &shellpipe, &shellquote]
    let l += [&shellredir, &shellxescape, &shellxquote]
    call writefile([json_encode(l)], 'Xtestout')
    qall!
  END
  for e in shells
    if RunVim([], after, '--cmd "set shell=' .. e[0] .. '"')
      call assert_equal(e, json_decode(readfile('Xtestout')[0]))
    endif
  endfor

  " Test shellescape() for each of the shells.
  for e in shells
    exe 'set shell=' .. e[0]
    if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$'
      let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%# \\'\\'' \\\\! \\% \\#'"
      let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\# \\'\\'' \\\\\\! \\\\% \\\\#'"
    elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$'
          \ || e[0] =~# '.*pwsh$' || e[0] =~# '.*pwsh.exe$'
      let str1 = "'cmd \"arg1\" ''arg2'' !%# \\'' \\! \\% \\#'"
      let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\# \\'' \\\\! \\\\% \\\\#'"
    elseif e[0] =~# '.*fish$' || e[0] =~# '.*fish.exe$'
      let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\\\'\\'' \\\\! \\\\% \\\\#'"
      let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\\\'\\'' \\\\\\! \\\\\\% \\\\\\#'"
    else
      let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\'\\'' \\! \\% \\#'"
      let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\'\\'' \\\\! \\\\% \\\\#'"
    endif
    call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%# \\' \\! \\% \\#"), e[0])
    call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%# \\' \\! \\% \\#", 1), e[0])

    " Try running an external command with the shell.
    if executable(e[0])
      " set the shell options for the current 'shell'
      let [&shellcmdflag, &shellpipe, &shellquote, &shellredir,
            \ &shellxescape, &shellxquote] = e[1:6]
      new
      try
        r !echo hello
        call assert_equal('hello', substitute(getline(2), '\W', '', 'g'), e[0])
      catch
        call assert_report('Failed to run shell command, shell: ' .. e[0]
              \ .. ', caught ' .. v:exception)
      finally
        bwipe!
      endtry

      " filter buffer contents through an external command
      new
      call setline(1, ['tom', 'sam', 'andy'])
      try
        %!sort
        call assert_equal(['andy', 'sam', 'tom'], getline(1, '$'), e[0])
      catch
        call assert_report($'Failed to filter buffer contents, shell: {e[0]}, caught {v:exception}')
      finally
        bwipe!
      endtry
    endif
  endfor
  set shell& shellcmdflag& shellpipe& shellquote&
  set shellredir& shellxescape& shellxquote&
  call delete('Xtestout')
endfunc

" Test for the 'shell' option
func Test_shell()
  CheckUnix
  let save_shell = &shell
  set shell=
  let caught_e91 = 0
  try
    shell
  catch /E91:/
    let caught_e91 = 1
  endtry
  call assert_equal(1, caught_e91)
  let &shell = save_shell
endfunc

" Test for the 'shellquote' option
func Test_shellquote()
  CheckUnix
  set shellquote=#
  set verbose=20
  redir => v
  silent! !echo Hello
  redir END
  set verbose&
  set shellquote&
  call assert_match(': "#echo Hello#"', v)
endfunc

" Test for the 'shellescape' option
func Test_shellescape()
  let save_shell = &shell
  set shell=bash
  call assert_equal("'text'", shellescape('text'))
  call assert_equal("'te\"xt'", 'te"xt'->shellescape())
  call assert_equal("'te'\\''xt'", shellescape("te'xt"))

  call assert_equal("'te%xt'", shellescape("te%xt"))
  call assert_equal("'te\\%xt'", shellescape("te%xt", 1))
  call assert_equal("'te#xt'", shellescape("te#xt"))
  call assert_equal("'te\\#xt'", shellescape("te#xt", 1))
  call assert_equal("'te!xt'", shellescape("te!xt"))
  call assert_equal("'te\\!xt'", shellescape("te!xt", 1))

  call assert_equal("'te\nxt'", shellescape("te\nxt"))
  call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1))
  set shell=tcsh
  call assert_equal("'te\\!xt'", shellescape("te!xt"))
  call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1))
  call assert_equal("'te\\\nxt'", shellescape("te\nxt"))
  call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1))

  let &shell = save_shell
endfunc

" Test for 'shellslash'
func Test_shellslash()
  CheckOption shellslash
  let save_shellslash = &shellslash
  " The shell and cmdflag, and expected slash in tempname with shellslash set or
  " unset.  The assert checks the file separator before the leafname.
  " ".*\\\\[^\\\\]*$"
  let shells = [['cmd', '/c', '\\', '/'],
        \ ['powershell', '-Command', '\\', '/'],
        \ ['pwsh', '-Command', '\\', '/'],
        \ ['pwsh', '-c', '\\', '/'],
        \ ['sh', '-c', '/', '/']]
  for e in shells
    exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1]
    set noshellslash
    let file = tempname()
    call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl')
    set shellslash
    let file = tempname()
    call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl')
  endfor
  let &shellslash = save_shellslash
endfunc

" Test for 'shellxquote'
func Test_shellxquote()
  CheckUnix

  let save_shell = &shell
  let save_sxq = &shellxquote
  let save_sxe = &shellxescape

  call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
  call setfperm('Xtestshell', "r-x------")
  set shell=./Xtestshell

  set shellxquote=\\"
  call feedkeys(":!pwd\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog'))

  set shellxquote=(
  call feedkeys(":!pwd\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog'))

  set shellxquote=\\"(
  call feedkeys(":!pwd\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog'))

  set shellxescape=\"&<<()@^
  set shellxquote=(
  call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt')
  call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog'))

  let &shell = save_shell
  let &shellxquote = save_sxq
  let &shellxescape = save_sxe
  call delete('Xlog')
endfunc

" Test for using the shell set in the $SHELL environment variable
func Test_set_shell()
  let after =<< trim [CODE]
    call writefile([&shell], "Xtestout")
    quit!
  [CODE]

  if has('win32')
    let $SHELL = 'C:\with space\cmd.exe'
    let expected = '"C:\with space\cmd.exe"'
  else
    let $SHELL = '/bin/with space/sh'
    let expected = '/bin/with\ space/sh'
  endif

  if RunVimPiped([], after, '', '')
    let lines = readfile('Xtestout')
    call assert_equal(expected, lines[0])
  endif
  call delete('Xtestout')
endfunc

func Test_shell_repeat()
  CheckUnix

  let save_shell = &shell

  call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
  call setfperm('Xtestshell', "r-x------")
  set shell=./Xtestshell
  defer delete('Xlog')

  call feedkeys(":!echo coconut\<CR>", 'xt')   " Run command
  call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))

  call feedkeys(":!!\<CR>", 'xt')              " Re-run previous
  call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))

  call writefile(['empty'], 'Xlog')
  call feedkeys(":!\<CR>", 'xt')               " :!
  call assert_equal(['Cmd: [-c ]'], readfile('Xlog'))

  call feedkeys(":!!\<CR>", 'xt')              " :! doesn't clear previous command
  call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))

  call feedkeys(":!echo banana\<CR>", 'xt')    " Make sure setting previous command keeps working after a :! no-op
  call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))
  call feedkeys(":!!\<CR>", 'xt')
  call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))

  let &shell = save_shell
endfunc

func Test_shell_no_prevcmd()
  " this doesn't do anything, just check it doesn't crash
  let after =<< trim END
    exe "normal !!\<CR>"
    call writefile([v:errmsg, 'done'], 'Xtestdone')
    qall!
  END
  if RunVim([], after, '--clean')
    call assert_equal(['E34: No previous command', 'done'], readfile('Xtestdone'))
  endif
  call delete('Xtestdone')
endfunc

" vim: shiftwidth=2 sts=2 expandtab