fileio_spec.lua 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. local helpers = require('test.functional.helpers')(after_each)
  2. local assert_log = helpers.assert_log
  3. local assert_nolog = helpers.assert_nolog
  4. local clear = helpers.clear
  5. local command = helpers.command
  6. local eq = helpers.eq
  7. local ok = helpers.ok
  8. local feed = helpers.feed
  9. local funcs = helpers.funcs
  10. local nvim_prog = helpers.nvim_prog
  11. local request = helpers.request
  12. local retry = helpers.retry
  13. local rmdir = helpers.rmdir
  14. local matches = helpers.matches
  15. local mkdir = helpers.mkdir
  16. local sleep = helpers.sleep
  17. local read_file = helpers.read_file
  18. local tmpname = helpers.tmpname
  19. local trim = helpers.trim
  20. local currentdir = helpers.funcs.getcwd
  21. local iswin = helpers.iswin
  22. local assert_alive = helpers.assert_alive
  23. local expect_exit = helpers.expect_exit
  24. local write_file = helpers.write_file
  25. local Screen = require('test.functional.ui.screen')
  26. local feed_command = helpers.feed_command
  27. local uname = helpers.uname
  28. describe('fileio', function()
  29. before_each(function()
  30. end)
  31. after_each(function()
  32. expect_exit(command, ':qall!')
  33. os.remove('Xtest_startup_shada')
  34. os.remove('Xtest_startup_file1')
  35. os.remove('Xtest_startup_file1~')
  36. os.remove('Xtest_startup_file2')
  37. os.remove('Xtest_тест.md')
  38. os.remove('Xtest-u8-int-max')
  39. os.remove('Xtest-overwrite-forced')
  40. rmdir('Xtest_startup_swapdir')
  41. rmdir('Xtest_backupdir')
  42. end)
  43. it('fsync() codepaths #8304', function()
  44. clear({ args={ '-i', 'Xtest_startup_shada',
  45. '--cmd', 'set directory=Xtest_startup_swapdir' } })
  46. -- These cases ALWAYS force fsync (regardless of 'fsync' option):
  47. -- 1. Idle (CursorHold) with modified buffers (+ 'swapfile').
  48. command('write Xtest_startup_file1')
  49. feed('ifoo<esc>h')
  50. command('write')
  51. eq(0, request('nvim__stats').fsync) -- 'nofsync' is the default.
  52. command('set swapfile')
  53. command('set updatetime=1')
  54. feed('izub<esc>h') -- File is 'modified'.
  55. sleep(3) -- Allow 'updatetime' to expire.
  56. retry(3, nil, function()
  57. eq(1, request('nvim__stats').fsync)
  58. end)
  59. command('set updatetime=9999')
  60. -- 2. Exit caused by deadly signal (+ 'swapfile').
  61. local j = funcs.jobstart({ nvim_prog, '-u', 'NONE', '-i',
  62. 'Xtest_startup_shada', '--headless',
  63. '-c', 'set swapfile',
  64. '-c', 'write Xtest_startup_file2',
  65. '-c', 'put =localtime()', })
  66. sleep(10) -- Let Nvim start.
  67. funcs.jobstop(j) -- Send deadly signal.
  68. -- 3. SIGPWR signal.
  69. -- ??
  70. -- 4. Explicit :preserve command.
  71. command('preserve')
  72. eq(2, request('nvim__stats').fsync)
  73. -- 5. Enable 'fsync' option, write file.
  74. command('set fsync')
  75. feed('ibaz<esc>h')
  76. command('write')
  77. eq(4, request('nvim__stats').fsync)
  78. end)
  79. it('backup #9709', function()
  80. if uname() == 'freebsd' then
  81. pending('Failing FreeBSD test')
  82. end
  83. clear({ args={ '-i', 'Xtest_startup_shada',
  84. '--cmd', 'set directory=Xtest_startup_swapdir' } })
  85. command('write Xtest_startup_file1')
  86. feed('ifoo<esc>')
  87. command('set backup')
  88. command('set backupcopy=yes')
  89. command('write')
  90. feed('Abar<esc>')
  91. command('write')
  92. local foobar_contents = trim(read_file('Xtest_startup_file1'))
  93. local bar_contents = trim(read_file('Xtest_startup_file1~'))
  94. eq('foobar', foobar_contents);
  95. eq('foo', bar_contents);
  96. end)
  97. it('backup with full path #11214', function()
  98. if uname() == 'freebsd' then
  99. pending('Failing FreeBSD test')
  100. end
  101. clear()
  102. mkdir('Xtest_backupdir')
  103. command('set backup')
  104. command('set backupdir=Xtest_backupdir//')
  105. command('write Xtest_startup_file1')
  106. feed('ifoo<esc>')
  107. command('write')
  108. feed('Abar<esc>')
  109. command('write')
  110. -- Backup filename = fullpath, separators replaced with "%".
  111. local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1',
  112. iswin() and '[:/\\]' or '/', '%%') .. '~'
  113. local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name))
  114. local foobar_contents = trim(read_file('Xtest_startup_file1'))
  115. eq('foobar', foobar_contents);
  116. eq('foo', foo_contents);
  117. end)
  118. it('readfile() on multibyte filename #10586', function()
  119. clear()
  120. local text = {
  121. 'line1',
  122. ' ...line2... ',
  123. '',
  124. 'line3!',
  125. 'тест yay тест.',
  126. '',
  127. }
  128. local fname = 'Xtest_тест.md'
  129. funcs.writefile(text, fname, 's')
  130. table.insert(text, '')
  131. eq(text, funcs.readfile(fname, 'b'))
  132. end)
  133. it('read invalid u8 over INT_MAX doesn\'t segfault', function()
  134. clear()
  135. command('call writefile(0zFFFFFFFF, "Xtest-u8-int-max")')
  136. -- This should not segfault
  137. command('edit ++enc=utf32 Xtest-u8-int-max')
  138. assert_alive()
  139. end)
  140. it(':w! does not show "file has been changed" warning', function()
  141. clear()
  142. write_file("Xtest-overwrite-forced", 'foobar')
  143. command('set nofixendofline')
  144. local screen = Screen.new(40,4)
  145. screen:set_default_attr_ids({
  146. [1] = {bold = true, foreground = Screen.colors.Blue1},
  147. [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
  148. [3] = {bold = true, foreground = Screen.colors.SeaGreen4}
  149. })
  150. screen:attach()
  151. command("set display-=msgsep shortmess-=F")
  152. command("e Xtest-overwrite-forced")
  153. screen:expect([[
  154. ^foobar |
  155. {1:~ }|
  156. {1:~ }|
  157. "Xtest-overwrite-forced" [noeol] 1L, 6B |
  158. ]])
  159. -- Get current unix time.
  160. local cur_unix_time = os.time(os.date("!*t"))
  161. local future_time = cur_unix_time + 999999
  162. -- Set the file's access/update time to be
  163. -- greater than the time at which it was created.
  164. local uv = require("luv")
  165. uv.fs_utime('Xtest-overwrite-forced', future_time, future_time)
  166. -- use async feed_command because nvim basically hangs on the prompt
  167. feed_command("w")
  168. screen:expect([[
  169. {2:WARNING: The file has been changed since}|
  170. {2: reading it!!!} |
  171. {3:Do you really want to write to it (y/n)^?}|
  172. |
  173. ]])
  174. feed("n")
  175. feed("<cr>")
  176. screen:expect([[
  177. ^foobar |
  178. {1:~ }|
  179. {1:~ }|
  180. |
  181. ]])
  182. -- Use a screen test because the warning does not set v:errmsg.
  183. command("w!")
  184. screen:expect([[
  185. ^foobar |
  186. {1:~ }|
  187. {1:~ }|
  188. <erwrite-forced" [noeol] 1L, 6B written |
  189. ]])
  190. end)
  191. end)
  192. describe('tmpdir', function()
  193. local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=]
  194. local testlog = 'Xtest_tmpdir_log'
  195. local faketmp
  196. before_each(function()
  197. -- Fake /tmp dir so that we can mess it up.
  198. faketmp = tmpname()
  199. os.remove(faketmp)
  200. mkdir(faketmp)
  201. end)
  202. after_each(function()
  203. expect_exit(command, ':qall!')
  204. os.remove(testlog)
  205. end)
  206. it('failure modes', function()
  207. clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } })
  208. assert_nolog('tempdir is not a directory', testlog)
  209. assert_nolog('tempdir has invalid permissions', testlog)
  210. -- Tempfiles typically look like: "…/nvim.<user>/xxx/0".
  211. -- - "…/nvim.<user>/xxx/" is the per-process tmpdir, not shared with other Nvims.
  212. -- - "…/nvim.<user>/" is the tmpdir root, shared by all Nvims (normally).
  213. local tmproot = (funcs.tempname()):match(tmproot_pat)
  214. ok(tmproot:len() > 4, 'tmproot like "nvim.foo"', tmproot)
  215. -- Test how Nvim handles invalid tmpdir root (by hostile users or accidents).
  216. --
  217. -- "…/nvim.<user>/" is not a directory:
  218. expect_exit(command, ':qall!')
  219. rmdir(tmproot)
  220. write_file(tmproot, '') -- Not a directory, vim_mktempdir() should skip it.
  221. clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } })
  222. matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir().
  223. -- Assert that broken tmpdir root was handled.
  224. retry(nil, 1000, function()
  225. assert_log('tempdir root not a directory', testlog, 100)
  226. end)
  227. -- "…/nvim.<user>/" has wrong permissions:
  228. if iswin() then
  229. return -- TODO(justinmk): need setfperm/getfperm on Windows. #8244
  230. end
  231. os.remove(testlog)
  232. os.remove(tmproot)
  233. mkdir(tmproot)
  234. funcs.setfperm(tmproot, 'rwxr--r--') -- Invalid permissions, vim_mktempdir() should skip it.
  235. clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } })
  236. matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir().
  237. -- Assert that broken tmpdir root was handled.
  238. retry(nil, 1000, function()
  239. assert_log('tempdir root has invalid permissions', testlog, 100)
  240. end)
  241. end)
  242. it('too long', function()
  243. local bigname = ('%s/%s'):format(faketmp, ('x'):rep(666))
  244. mkdir(bigname)
  245. clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=bigname, } })
  246. matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir().
  247. local len = (funcs.tempname()):len()
  248. ok(len > 4 and len < 256, '4 < len < 256', tostring(len))
  249. end)
  250. end)