startup_spec.lua 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. local helpers = require('test.functional.helpers')(after_each)
  2. local Screen = require('test.functional.ui.screen')
  3. local clear = helpers.clear
  4. local command = helpers.command
  5. local ok = helpers.ok
  6. local eq = helpers.eq
  7. local matches = helpers.matches
  8. local eval = helpers.eval
  9. local exec_lua = helpers.exec_lua
  10. local feed = helpers.feed
  11. local funcs = helpers.funcs
  12. local mkdir = helpers.mkdir
  13. local mkdir_p = helpers.mkdir_p
  14. local nvim_prog = helpers.nvim_prog
  15. local nvim_set = helpers.nvim_set
  16. local read_file = helpers.read_file
  17. local retry = helpers.retry
  18. local rmdir = helpers.rmdir
  19. local sleep = helpers.sleep
  20. local iswin = helpers.iswin
  21. local write_file = helpers.write_file
  22. local meths = helpers.meths
  23. describe('startup', function()
  24. before_each(function()
  25. clear()
  26. os.remove('Xtest_startup_ttyout')
  27. end)
  28. after_each(function()
  29. os.remove('Xtest_startup_ttyout')
  30. end)
  31. it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
  32. -- system() puts a pipe at both ends.
  33. local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
  34. '--cmd', nvim_set,
  35. '-c', [[echo has('ttyin') has('ttyout')]],
  36. '+q' })
  37. eq('0 0', out)
  38. end)
  39. it('with --embed: has("ttyin")==0 has("ttyout")==0', function()
  40. local screen = Screen.new(25, 3)
  41. -- Remote UI connected by --embed.
  42. screen:attach()
  43. command([[echo has('ttyin') has('ttyout')]])
  44. screen:expect([[
  45. ^ |
  46. ~ |
  47. 0 0 |
  48. ]])
  49. end)
  50. it('in a TTY: has("ttyin")==1 has("ttyout")==1', function()
  51. local screen = Screen.new(25, 4)
  52. screen:attach()
  53. if iswin() then
  54. command([[set shellcmdflag=/s\ /c shellxquote=\"]])
  55. end
  56. -- Running in :terminal
  57. command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
  58. ..nvim_set..[[\"]]
  59. ..[[ -c \"echo has('ttyin') has('ttyout')\""]]
  60. ..[[, shellescape(v:progpath))]])
  61. screen:expect([[
  62. ^ |
  63. ~ |
  64. 1 1 |
  65. |
  66. ]])
  67. end)
  68. it('output to pipe: has("ttyin")==1 has("ttyout")==0', function()
  69. if iswin() then
  70. command([[set shellcmdflag=/s\ /c shellxquote=\"]])
  71. end
  72. -- Running in :terminal
  73. command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
  74. ..nvim_set..[[\"]]
  75. ..[[ -c \"call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')\"]]
  76. ..[[ -c q | cat -v"]] -- Output to a pipe.
  77. ..[[, shellescape(v:progpath))]])
  78. retry(nil, 3000, function()
  79. sleep(1)
  80. eq('1\n0\n', -- stdin is a TTY, stdout is a pipe
  81. read_file('Xtest_startup_ttyout'))
  82. end)
  83. end)
  84. it('input from pipe: has("ttyin")==0 has("ttyout")==1', function()
  85. if iswin() then
  86. command([[set shellcmdflag=/s\ /c shellxquote=\"]])
  87. end
  88. -- Running in :terminal
  89. command([[exe printf("terminal echo foo | ]] -- Input from a pipe.
  90. ..[[%s -u NONE -i NONE --cmd \"]]
  91. ..nvim_set..[[\"]]
  92. ..[[ -c \"call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')\"]]
  93. ..[[ -c q -- -"]]
  94. ..[[, shellescape(v:progpath))]])
  95. retry(nil, 3000, function()
  96. sleep(1)
  97. eq('0\n1\n', -- stdin is a pipe, stdout is a TTY
  98. read_file('Xtest_startup_ttyout'))
  99. end)
  100. end)
  101. it('input from pipe (implicit) #7679', function()
  102. local screen = Screen.new(25, 4)
  103. screen:attach()
  104. if iswin() then
  105. command([[set shellcmdflag=/s\ /c shellxquote=\"]])
  106. end
  107. -- Running in :terminal
  108. command([[exe printf("terminal echo foo | ]] -- Input from a pipe.
  109. ..[[%s -u NONE -i NONE --cmd \"]]
  110. ..nvim_set..[[\"]]
  111. ..[[ -c \"echo has('ttyin') has('ttyout')\""]]
  112. ..[[, shellescape(v:progpath))]])
  113. screen:expect([[
  114. ^foo |
  115. ~ |
  116. 0 1 |
  117. |
  118. ]])
  119. end)
  120. it('input from pipe + file args #7679', function()
  121. eq('ohyeah\r\n0 0 bufs=3',
  122. funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '--headless',
  123. '+.print',
  124. "+echo has('ttyin') has('ttyout') 'bufs='.bufnr('$')",
  125. '+qall!',
  126. '-',
  127. 'test/functional/fixtures/tty-test.c',
  128. 'test/functional/fixtures/shell-test.c',
  129. },
  130. { 'ohyeah', '' }))
  131. end)
  132. it('if stdin is empty: selects buffer 2, deletes buffer 1 #8561', function()
  133. eq('\r\n 2 %a "file1" line 0\r\n 3 "file2" line 0',
  134. funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '--headless',
  135. '+ls!',
  136. '+qall!',
  137. '-',
  138. 'file1',
  139. 'file2',
  140. },
  141. { '' }))
  142. end)
  143. it('-e/-E interactive #7679', function()
  144. clear('-e')
  145. local screen = Screen.new(25, 3)
  146. screen:attach()
  147. feed("put ='from -e'<CR>")
  148. screen:expect([[
  149. :put ='from -e' |
  150. from -e |
  151. :^ |
  152. ]])
  153. clear('-E')
  154. screen = Screen.new(25, 3)
  155. screen:attach()
  156. feed("put ='from -E'<CR>")
  157. screen:expect([[
  158. :put ='from -E' |
  159. from -E |
  160. :^ |
  161. ]])
  162. end)
  163. it('stdin with -es/-Es #7679', function()
  164. local input = { 'append', 'line1', 'line2', '.', '%print', '' }
  165. local inputstr = table.concat(input, '\n')
  166. --
  167. -- -Es: read stdin as text
  168. --
  169. eq('partylikeits1999\n',
  170. funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '-Es', '+.print', 'test/functional/fixtures/tty-test.c' },
  171. { 'partylikeits1999', '' }))
  172. eq(inputstr,
  173. funcs.system({nvim_prog, '-i', 'NONE', '-Es', '+%print', '-' },
  174. input))
  175. -- with `-u NORC`
  176. eq('thepartycontinues\n',
  177. funcs.system({nvim_prog, '-n', '-u', 'NORC', '-Es', '+.print' },
  178. { 'thepartycontinues', '' }))
  179. -- without `-u`
  180. eq('thepartycontinues\n',
  181. funcs.system({nvim_prog, '-n', '-Es', '+.print' },
  182. { 'thepartycontinues', '' }))
  183. --
  184. -- -es: read stdin as ex-commands
  185. --
  186. eq(' encoding=utf-8\n',
  187. funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '-es', 'test/functional/fixtures/tty-test.c' },
  188. { 'set encoding', '' }))
  189. eq('line1\nline2\n',
  190. funcs.system({nvim_prog, '-i', 'NONE', '-es', '-' },
  191. input))
  192. -- with `-u NORC`
  193. eq(' encoding=utf-8\n',
  194. funcs.system({nvim_prog, '-n', '-u', 'NORC', '-es' },
  195. { 'set encoding', '' }))
  196. -- without `-u`
  197. eq(' encoding=utf-8\n',
  198. funcs.system({nvim_prog, '-n', '-es' },
  199. { 'set encoding', '' }))
  200. end)
  201. it('-es/-Es disables swapfile, user config #8540', function()
  202. for _,arg in ipairs({'-es', '-Es'}) do
  203. local out = funcs.system({nvim_prog, arg,
  204. '+set swapfile? updatecount? shada?',
  205. "+put =execute('scriptnames')", '+%print'})
  206. local line1 = string.match(out, '^.-\n')
  207. -- updatecount=0 means swapfile was disabled.
  208. eq(" swapfile updatecount=0 shada=!,'100,<50,s10,h\n", line1)
  209. -- Standard plugins were loaded, but not user config.
  210. eq('health.vim', string.match(out, 'health.vim'))
  211. eq(nil, string.match(out, 'init.vim'))
  212. end
  213. end)
  214. it('fails on --embed with -es/-Es', function()
  215. matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es',
  216. funcs.system({nvim_prog, '--embed', '-es' }))
  217. matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es',
  218. funcs.system({nvim_prog, '--embed', '-Es' }))
  219. end)
  220. it('does not crash if --embed is given twice', function()
  221. clear{args={'--embed'}}
  222. eq(2, eval('1+1'))
  223. end)
  224. it('does not crash when expanding cdpath during early_init', function()
  225. clear{env={CDPATH='~doesnotexist'}}
  226. eq(',~doesnotexist', eval('&cdpath'))
  227. end)
  228. it('ENTER dismisses early message #7967', function()
  229. local screen
  230. screen = Screen.new(60, 6)
  231. screen:attach()
  232. command([[let g:id = termopen('"]]..nvim_prog..
  233. [[" -u NONE -i NONE --cmd "set noruler" --cmd "let g:foo = g:bar"')]])
  234. screen:expect([[
  235. ^ |
  236. Error detected while processing pre-vimrc command line: |
  237. E121: Undefined variable: g:bar |
  238. E15: Invalid expression: g:bar |
  239. Press ENTER or type command to continue |
  240. |
  241. ]])
  242. command([[call chansend(g:id, "\n")]])
  243. screen:expect([[
  244. ^ |
  245. ~ |
  246. ~ |
  247. [No Name] |
  248. |
  249. |
  250. ]])
  251. end)
  252. it("sets 'shortmess' when loading other tabs", function()
  253. clear({args={'-p', 'a', 'b', 'c'}})
  254. local screen = Screen.new(25, 4)
  255. screen:attach()
  256. screen:expect({grid=[[
  257. {1: a }{2: b c }{3: }{2:X}|
  258. ^ |
  259. {4:~ }|
  260. |
  261. ]],
  262. attr_ids={
  263. [1] = {bold = true},
  264. [2] = {background = Screen.colors.LightGrey, underline = true},
  265. [3] = {reverse = true},
  266. [4] = {bold = true, foreground = Screen.colors.Blue1},
  267. }})
  268. end)
  269. it('fixed hang issue with --headless (#11386)', function()
  270. local expected = ''
  271. local period = 100
  272. for i = 1, period - 1 do
  273. expected = expected .. i .. '\r\n'
  274. end
  275. expected = expected .. period
  276. eq(
  277. expected,
  278. -- FIXME(codehex): We should really set a timeout for the system function.
  279. -- If this test fails, there will be a waiting input state.
  280. funcs.system({nvim_prog, '-u', 'NONE', '-c',
  281. 'for i in range(1, 100) | echo i | endfor | quit',
  282. '--headless'
  283. })
  284. )
  285. end)
  286. it("get command line arguments from v:argv", function()
  287. local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
  288. '--cmd', nvim_set,
  289. '-c', [[echo v:argv[-1:] len(v:argv) > 1]],
  290. '+q' })
  291. eq('[\'+q\'] 1', out)
  292. end)
  293. local function pack_clear(cmd)
  294. clear('--cmd', 'set packpath=test/functional/fixtures', '--cmd', cmd)
  295. end
  296. it("handles &packpath during startup", function()
  297. pack_clear [[
  298. let g:x = bar#test()
  299. let g:y = leftpad#pad("heyya")
  300. ]]
  301. eq(-3, eval 'g:x')
  302. eq(" heyya", eval 'g:y')
  303. pack_clear [[ lua _G.y = require'bar'.doit() _G.z = require'leftpad''howdy' ]]
  304. eq({9003, '\thowdy'}, exec_lua [[ return { _G.y, _G.z } ]])
  305. end)
  306. it("handles :packadd during startup", function()
  307. -- control group: opt/bonus is not availabe by default
  308. pack_clear [[
  309. try
  310. let g:x = bonus#secret()
  311. catch
  312. let g:err = v:exception
  313. endtry
  314. ]]
  315. eq('Vim(let):E117: Unknown function: bonus#secret', eval 'g:err')
  316. pack_clear [[ lua _G.test = {pcall(function() require'bonus'.launch() end)} ]]
  317. eq({false, [[[string ":lua"]:1: module 'bonus' not found:]]},
  318. exec_lua [[ _G.test[2] = string.gsub(_G.test[2], '[\r\n].*', '') return _G.test ]])
  319. -- ok, time to launch the nukes:
  320. pack_clear [[ packadd! bonus | let g:x = bonus#secret() ]]
  321. eq('halloj', eval 'g:x')
  322. pack_clear [[ packadd! bonus | lua _G.y = require'bonus'.launch() ]]
  323. eq('CPE 1704 TKS', exec_lua [[ return _G.y ]])
  324. end)
  325. end)
  326. describe('sysinit', function()
  327. local xdgdir = 'Xxdg'
  328. local vimdir = 'Xvim'
  329. local xhome = 'Xhome'
  330. local pathsep = helpers.get_pathsep()
  331. before_each(function()
  332. rmdir(xdgdir)
  333. rmdir(vimdir)
  334. rmdir(xhome)
  335. mkdir(xdgdir)
  336. mkdir(xdgdir .. pathsep .. 'nvim')
  337. write_file(table.concat({xdgdir, 'nvim', 'sysinit.vim'}, pathsep), [[
  338. let g:loaded = get(g:, "loaded", 0) + 1
  339. let g:xdg = 1
  340. ]])
  341. mkdir(vimdir)
  342. write_file(table.concat({vimdir, 'sysinit.vim'}, pathsep), [[
  343. let g:loaded = get(g:, "loaded", 0) + 1
  344. let g:vim = 1
  345. ]])
  346. mkdir(xhome)
  347. end)
  348. after_each(function()
  349. rmdir(xdgdir)
  350. rmdir(vimdir)
  351. rmdir(xhome)
  352. end)
  353. it('prefers XDG_CONFIG_DIRS over VIM', function()
  354. clear{args={'--cmd', 'set nomore undodir=. directory=. belloff='},
  355. args_rm={'-u', '--cmd'},
  356. env={ HOME=xhome,
  357. XDG_CONFIG_DIRS=xdgdir,
  358. VIM=vimdir }}
  359. eq('loaded 1 xdg 1 vim 0',
  360. eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
  361. end)
  362. it('uses VIM if XDG_CONFIG_DIRS unset', function()
  363. clear{args={'--cmd', 'set nomore undodir=. directory=. belloff='},
  364. args_rm={'-u', '--cmd'},
  365. env={ HOME=xhome,
  366. XDG_CONFIG_DIRS='',
  367. VIM=vimdir }}
  368. eq('loaded 1 xdg 0 vim 1',
  369. eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
  370. end)
  371. it('fixed hang issue with -D (#12647)', function()
  372. local screen
  373. screen = Screen.new(60, 6)
  374. screen:attach()
  375. command([[let g:id = termopen('"]]..nvim_prog..
  376. [[" -u NONE -i NONE --cmd "set noruler" -D')]])
  377. screen:expect([[
  378. ^ |
  379. Entering Debug mode. Type "cont" to continue. |
  380. cmd: augroup nvim_terminal |
  381. > |
  382. <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
  383. |
  384. ]])
  385. command([[call chansend(g:id, "cont\n")]])
  386. screen:expect([[
  387. ^ |
  388. ~ |
  389. [No Name] |
  390. |
  391. <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All|
  392. |
  393. ]])
  394. end)
  395. end)
  396. describe('clean', function()
  397. clear()
  398. ok(string.find(meths.get_option('runtimepath'), funcs.stdpath('config'), 1, true) ~= nil)
  399. clear('--clean')
  400. ok(string.find(meths.get_option('runtimepath'), funcs.stdpath('config'), 1, true) == nil)
  401. end)
  402. describe('user config init', function()
  403. local xhome = 'Xhome'
  404. local pathsep = helpers.get_pathsep()
  405. local xconfig = xhome .. pathsep .. 'Xconfig'
  406. local init_lua_path = table.concat({xconfig, 'nvim', 'init.lua'}, pathsep)
  407. before_each(function()
  408. rmdir(xhome)
  409. mkdir_p(xconfig .. pathsep .. 'nvim')
  410. write_file(init_lua_path, [[
  411. vim.g.lua_rc = 1
  412. ]])
  413. end)
  414. after_each(function()
  415. rmdir(xhome)
  416. end)
  417. it('loads init.lua from XDG config home by default', function()
  418. clear{ args_rm={'-u' }, env={ XDG_CONFIG_HOME=xconfig }}
  419. eq(1, eval('g:lua_rc'))
  420. eq(init_lua_path, eval('$MYVIMRC'))
  421. end)
  422. describe 'with explicitly provided config'(function()
  423. local custom_lua_path = table.concat({xhome, 'custom.lua'}, pathsep)
  424. before_each(function()
  425. write_file(custom_lua_path, [[
  426. vim.g.custom_lua_rc = 1
  427. ]])
  428. end)
  429. it('loads custom lua config and does not set $MYVIMRC', function()
  430. clear{ args={'-u', custom_lua_path }, env={ XDG_CONFIG_HOME=xconfig }}
  431. eq(1, eval('g:custom_lua_rc'))
  432. eq('', eval('$MYVIMRC'))
  433. end)
  434. end)
  435. describe 'VIMRC also exists'(function()
  436. before_each(function()
  437. write_file(table.concat({xconfig, 'nvim', 'init.vim'}, pathsep), [[
  438. let g:vim_rc = 1
  439. ]])
  440. end)
  441. it('loads default lua config, but shows an error', function()
  442. clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig }}
  443. feed('<cr>') -- TODO check this, test execution is blocked without it
  444. eq(1, eval('g:lua_rc'))
  445. matches('Conflicting configs', meths.exec('messages', true))
  446. end)
  447. end)
  448. end)
  449. describe('runtime:', function()
  450. local xhome = 'Xhome'
  451. local pathsep = helpers.get_pathsep()
  452. local xconfig = xhome .. pathsep .. 'Xconfig'
  453. setup(function()
  454. mkdir_p(xconfig .. pathsep .. 'nvim')
  455. end)
  456. teardown(function()
  457. rmdir(xhome)
  458. end)
  459. it('loads plugin/*.lua from XDG config home', function()
  460. local plugin_folder_path = table.concat({xconfig, 'nvim', 'plugin'}, pathsep)
  461. local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep)
  462. mkdir_p(plugin_folder_path)
  463. write_file(plugin_file_path, [[ vim.g.lua_plugin = 1 ]])
  464. clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig }}
  465. eq(1, eval('g:lua_plugin'))
  466. rmdir(plugin_folder_path)
  467. end)
  468. it('loads plugin/*.lua from start plugins', function()
  469. local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'catagory',
  470. 'start', 'test_plugin'}, pathsep)
  471. local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep)
  472. local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'},
  473. pathsep)
  474. local profiler_file = 'test_startuptime.log'
  475. mkdir_p(plugin_folder_path)
  476. write_file(plugin_file_path, [[vim.g.lua_plugin = 2]])
  477. clear{ args_rm={'-u'}, args={'--startuptime', profiler_file}, env={ XDG_CONFIG_HOME=xconfig }}
  478. eq(2, eval('g:lua_plugin'))
  479. -- Check if plugin_file_path is listed in :scriptname
  480. local scripts = meths.exec(':scriptnames', true)
  481. assert.Truthy(scripts:find(plugin_file_path))
  482. -- Check if plugin_file_path is listed in startup profile
  483. local profile_reader = io.open(profiler_file, 'r')
  484. local profile_log = profile_reader:read('*a')
  485. profile_reader:close()
  486. assert.Truthy(profile_log :find(plugin_file_path))
  487. os.remove(profiler_file)
  488. rmdir(plugin_path)
  489. end)
  490. it('loads ftdetect/*.lua', function()
  491. local ftdetect_folder = table.concat({xconfig, 'nvim', 'ftdetect'}, pathsep)
  492. local ftdetect_file = table.concat({ftdetect_folder , 'new-ft.lua'}, pathsep)
  493. mkdir_p(ftdetect_folder)
  494. write_file(ftdetect_file , [[vim.g.lua_ftdetect = 1]])
  495. -- TODO(shadmansaleh): Figure out why this test fails without
  496. -- setting VIMRUNTIME
  497. clear{ args_rm={'-u'}, env={XDG_CONFIG_HOME=xconfig,
  498. VIMRUNTIME='runtime/'}}
  499. eq(1, eval('g:lua_ftdetect'))
  500. rmdir(ftdetect_folder)
  501. end)
  502. end)
  503. describe('user session', function()
  504. local xhome = 'Xhome'
  505. local pathsep = helpers.get_pathsep()
  506. local session_file = table.concat({xhome, 'session.lua'}, pathsep)
  507. before_each(function()
  508. rmdir(xhome)
  509. mkdir(xhome)
  510. write_file(session_file, [[
  511. vim.g.lua_session = 1
  512. ]])
  513. end)
  514. after_each(function()
  515. rmdir(xhome)
  516. end)
  517. it('loads session from the provided lua file', function()
  518. clear{ args={'-S', session_file }, env={ HOME=xhome }}
  519. eq(1, eval('g:lua_session'))
  520. end)
  521. end)