123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- local t = require('test.testutil')
- local n = require('test.functional.testnvim')()
- local Screen = require('test.functional.ui.screen')
- local uv = vim.uv
- local assert_log = t.assert_log
- local assert_nolog = t.assert_nolog
- local clear = n.clear
- local command = n.command
- local eq = t.eq
- local neq = t.neq
- local ok = t.ok
- local feed = n.feed
- local fn = n.fn
- local nvim_prog = n.nvim_prog
- local request = n.request
- local retry = t.retry
- local rmdir = n.rmdir
- local matches = t.matches
- local api = n.api
- local mkdir = t.mkdir
- local sleep = vim.uv.sleep
- local read_file = t.read_file
- local trim = vim.trim
- local currentdir = n.fn.getcwd
- local assert_alive = n.assert_alive
- local check_close = n.check_close
- local expect_exit = n.expect_exit
- local write_file = t.write_file
- local feed_command = n.feed_command
- local skip = t.skip
- local is_os = t.is_os
- local is_ci = t.is_ci
- local spawn = n.spawn
- local set_session = n.set_session
- describe('fileio', function()
- before_each(function() end)
- after_each(function()
- check_close()
- os.remove('Xtest_startup_shada')
- os.remove('Xtest_startup_file1')
- os.remove('Xtest_startup_file1~')
- os.remove('Xtest_startup_file2')
- os.remove('Xtest_startup_file2~')
- os.remove('Xtest_тест.md')
- os.remove('Xtest-u8-int-max')
- os.remove('Xtest-overwrite-forced')
- rmdir('Xtest_startup_swapdir')
- rmdir('Xtest_backupdir')
- rmdir('Xtest_backupdir with spaces')
- end)
- local args = { nvim_prog, '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' }
- --- Starts a new nvim session and returns an attached screen.
- local function startup(extra_args)
- extra_args = extra_args or {}
- local argv = vim.iter({ args, '--embed', extra_args }):flatten():totable()
- local screen_nvim = spawn(argv)
- set_session(screen_nvim)
- local screen = Screen.new(70, 10)
- screen:set_default_attr_ids({
- [1] = { foreground = Screen.colors.NvimDarkGrey4 },
- [2] = { background = Screen.colors.NvimDarkGrey1, foreground = Screen.colors.NvimLightGrey3 },
- [3] = { foreground = Screen.colors.NvimLightCyan },
- })
- return screen
- end
- it("fsync() with 'nofsync' #8304", function()
- clear({ args = { '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } })
- -- These cases ALWAYS force fsync (regardless of 'fsync' option):
- -- 1. Idle (CursorHold) with modified buffers (+ 'swapfile').
- command('write Xtest_startup_file1')
- feed('Afoo<esc>h')
- command('write')
- eq(0, request('nvim__stats').fsync)
- command('set swapfile')
- command('set updatetime=1')
- feed('Azub<esc>h') -- File is 'modified'.
- sleep(3) -- Allow 'updatetime' to expire.
- retry(3, nil, function()
- eq(1, request('nvim__stats').fsync)
- end)
- command('set updatetime=100000 updatecount=100000')
- -- 2. Explicit :preserve command.
- command('preserve')
- -- TODO: should be exactly 2; where is the extra fsync() is coming from? #26404
- ok(request('nvim__stats').fsync == 2 or request('nvim__stats').fsync == 3)
- -- 3. Enable 'fsync' option, write file.
- command('set fsync')
- feed('Abaz<esc>h')
- command('write')
- -- TODO: should be exactly 4; where is the extra fsync() is coming from? #26404
- ok(request('nvim__stats').fsync == 4 or request('nvim__stats').fsync == 5)
- eq('foozubbaz', trim(read_file('Xtest_startup_file1')))
- -- 4. Exit caused by deadly signal (+ 'swapfile').
- local j = fn.jobstart(vim.iter({ args, '--embed' }):flatten():totable(), { rpc = true })
- fn.rpcrequest(
- j,
- 'nvim_exec2',
- [[
- set nofsync directory=Xtest_startup_swapdir
- edit Xtest_startup_file2
- write
- put ='fsyncd text'
- ]],
- {}
- )
- eq('Xtest_startup_swapdir', fn.rpcrequest(j, 'nvim_eval', '&directory'))
- fn.jobstop(j) -- Send deadly signal.
- local screen = startup()
- feed(':recover Xtest_startup_file2<cr>')
- screen:expect({ any = [[Using swap file "Xtest_startup_swapdir[/\]Xtest_startup_file2%.swp"]] })
- feed('<cr>')
- screen:expect({ any = 'fsyncd text' })
- -- 5. SIGPWR signal.
- -- oldtest: Test_signal_PWR()
- end)
- it('backup #9709', function()
- skip(is_ci('cirrus'))
- clear({
- args = {
- '-i',
- 'Xtest_startup_shada',
- '--cmd',
- 'set directory=Xtest_startup_swapdir',
- },
- })
- command('write Xtest_startup_file1')
- feed('ifoo<esc>')
- command('set backup')
- command('set backupcopy=yes')
- command('write')
- feed('Abar<esc>')
- command('write')
- local foobar_contents = trim(read_file('Xtest_startup_file1'))
- local bar_contents = trim(read_file('Xtest_startup_file1~'))
- eq('foobar', foobar_contents)
- eq('foo', bar_contents)
- end)
- it('backup with full path #11214', function()
- skip(is_ci('cirrus'))
- clear()
- mkdir('Xtest_backupdir')
- command('set backup')
- command('set backupdir=Xtest_backupdir//')
- command('write Xtest_startup_file1')
- feed('ifoo<esc>')
- command('write')
- feed('Abar<esc>')
- command('write')
- -- Backup filename = fullpath, separators replaced with "%".
- local backup_file_name = string.gsub(
- currentdir() .. '/Xtest_startup_file1',
- is_os('win') and '[:/\\]' or '/',
- '%%'
- ) .. '~'
- local foo_contents = trim(read_file('Xtest_backupdir/' .. backup_file_name))
- local foobar_contents = trim(read_file('Xtest_startup_file1'))
- eq('foobar', foobar_contents)
- eq('foo', foo_contents)
- end)
- it('backup with full path with spaces', function()
- skip(is_ci('cirrus'))
- clear()
- mkdir('Xtest_backupdir with spaces')
- command('set backup')
- command('set backupdir=Xtest_backupdir\\ with\\ spaces//')
- command('write Xtest_startup_file1')
- feed('ifoo<esc>')
- command('write')
- feed('Abar<esc>')
- command('write')
- -- Backup filename = fullpath, separators replaced with "%".
- local backup_file_name = string.gsub(
- currentdir() .. '/Xtest_startup_file1',
- is_os('win') and '[:/\\]' or '/',
- '%%'
- ) .. '~'
- local foo_contents = trim(read_file('Xtest_backupdir with spaces/' .. backup_file_name))
- local foobar_contents = trim(read_file('Xtest_startup_file1'))
- eq('foobar', foobar_contents)
- eq('foo', foo_contents)
- end)
- it('backup symlinked files #11349', function()
- skip(is_ci('cirrus'))
- clear()
- local initial_content = 'foo'
- local link_file_name = 'Xtest_startup_file2'
- local backup_file_name = link_file_name .. '~'
- write_file('Xtest_startup_file1', initial_content, false)
- uv.fs_symlink('Xtest_startup_file1', link_file_name)
- command('set backup')
- command('set backupcopy=yes')
- command('edit ' .. link_file_name)
- feed('Abar<esc>')
- command('write')
- local backup_raw = read_file(backup_file_name)
- neq(nil, backup_raw, 'Expected backup file ' .. backup_file_name .. 'to exist but did not')
- eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents')
- end)
- it('backup symlinked files in first available backupdir #11349', function()
- skip(is_ci('cirrus'))
- clear()
- local initial_content = 'foo'
- local backup_dir = 'Xtest_backupdir'
- local sep = n.get_pathsep()
- local link_file_name = 'Xtest_startup_file2'
- local backup_file_name = backup_dir .. sep .. link_file_name .. '~'
- write_file('Xtest_startup_file1', initial_content, false)
- uv.fs_symlink('Xtest_startup_file1', link_file_name)
- mkdir(backup_dir)
- command('set backup')
- command('set backupcopy=yes')
- command('set backupdir=.__this_does_not_exist__,' .. backup_dir)
- command('edit ' .. link_file_name)
- feed('Abar<esc>')
- command('write')
- local backup_raw = read_file(backup_file_name)
- neq(nil, backup_raw, 'Expected backup file ' .. backup_file_name .. ' to exist but did not')
- eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents')
- end)
- it('readfile() on multibyte filename #10586', function()
- clear()
- local text = {
- 'line1',
- ' ...line2... ',
- '',
- 'line3!',
- 'тест yay тест.',
- '',
- }
- local fname = 'Xtest_тест.md'
- fn.writefile(text, fname, 's')
- table.insert(text, '')
- eq(text, fn.readfile(fname, 'b'))
- end)
- it("read invalid u8 over INT_MAX doesn't segfault", function()
- clear()
- command('call writefile(0zFFFFFFFF, "Xtest-u8-int-max")')
- -- This should not segfault
- command('edit ++enc=utf32 Xtest-u8-int-max')
- assert_alive()
- end)
- it(':w! does not show "file has been changed" warning', function()
- clear()
- write_file('Xtest-overwrite-forced', 'foobar')
- command('set nofixendofline')
- local screen = Screen.new(40, 4)
- command('set shortmess-=F')
- command('e Xtest-overwrite-forced')
- screen:expect([[
- ^foobar |
- {1:~ }|*2
- "Xtest-overwrite-forced" [noeol] 1L, 6B |
- ]])
- -- Get current unix time.
- local cur_unix_time = os.time(os.date('!*t'))
- local future_time = cur_unix_time + 999999
- -- Set the file's access/update time to be
- -- greater than the time at which it was created.
- uv.fs_utime('Xtest-overwrite-forced', future_time, future_time)
- -- use async feed_command because nvim basically hangs on the prompt
- feed_command('w')
- screen:expect([[
- {9:WARNING: The file has been changed since}|
- {9: reading it!!!} |
- {6:Do you really want to write to it (y/n)?}|
- ^ |
- ]])
- feed('n')
- feed('<cr>')
- screen:expect([[
- ^foobar |
- {1:~ }|*2
- |
- ]])
- -- Use a screen test because the warning does not set v:errmsg.
- command('w!')
- screen:expect([[
- ^foobar |
- {1:~ }|*2
- <erwrite-forced" [noeol] 1L, 6B written |
- ]])
- end)
- end)
- describe('tmpdir', function()
- local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=]
- local testlog = 'Xtest_tmpdir_log'
- local os_tmpdir ---@type string
- before_each(function()
- -- Fake /tmp dir so that we can mess it up.
- os_tmpdir = assert(vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX'))
- end)
- after_each(function()
- check_close()
- os.remove(testlog)
- end)
- local function get_tmproot()
- -- Tempfiles typically look like: "…/nvim.<user>/xxx/0".
- -- - "…/nvim.<user>/xxx/" is the per-process tmpdir, not shared with other Nvims.
- -- - "…/nvim.<user>/" is the tmpdir root, shared by all Nvims (normally).
- local tmproot = (fn.tempname()):match(tmproot_pat)
- ok(tmproot:len() > 4, 'tmproot like "nvim.foo"', tmproot)
- return tmproot
- end
- it('failure modes', function()
- clear({ env = { NVIM_LOG_FILE = testlog, TMPDIR = os_tmpdir } })
- assert_nolog('tempdir is not a directory', testlog)
- assert_nolog('tempdir has invalid permissions', testlog)
- local tmproot = get_tmproot()
- -- Test how Nvim handles invalid tmpdir root (by hostile users or accidents).
- --
- -- "…/nvim.<user>/" is not a directory:
- expect_exit(command, ':qall!')
- rmdir(tmproot)
- write_file(tmproot, '') -- Not a directory, vim_mktempdir() should skip it.
- clear({ env = { NVIM_LOG_FILE = testlog, TMPDIR = os_tmpdir } })
- matches(tmproot_pat, fn.stdpath('run')) -- Tickle vim_mktempdir().
- -- Assert that broken tmpdir root was handled.
- assert_log('tempdir root not a directory', testlog, 100)
- -- "…/nvim.<user>/" has wrong permissions:
- skip(is_os('win'), 'TODO(justinmk): need setfperm/getfperm on Windows. #8244')
- os.remove(testlog)
- os.remove(tmproot)
- mkdir(tmproot)
- fn.setfperm(tmproot, 'rwxr--r--') -- Invalid permissions, vim_mktempdir() should skip it.
- clear({ env = { NVIM_LOG_FILE = testlog, TMPDIR = os_tmpdir } })
- matches(tmproot_pat, fn.stdpath('run')) -- Tickle vim_mktempdir().
- -- Assert that broken tmpdir root was handled.
- assert_log('tempdir root has invalid permissions', testlog, 100)
- end)
- it('too long', function()
- local bigname = ('%s/%s'):format(os_tmpdir, ('x'):rep(666))
- mkdir(bigname)
- clear({ env = { NVIM_LOG_FILE = testlog, TMPDIR = bigname } })
- matches(tmproot_pat, fn.stdpath('run')) -- Tickle vim_mktempdir().
- local len = (fn.tempname()):len()
- ok(len > 4 and len < 256, '4 < len < 256', tostring(len))
- end)
- it('disappeared #1432', function()
- clear({ env = { NVIM_LOG_FILE = testlog, TMPDIR = os_tmpdir } })
- assert_nolog('tempdir disappeared', testlog)
- local function rm_tmpdir()
- local tmpname1 = fn.tempname()
- local tmpdir1 = fn.fnamemodify(tmpname1, ':h')
- eq(fn.stdpath('run'), tmpdir1)
- rmdir(tmpdir1)
- retry(nil, 1000, function()
- eq(0, fn.isdirectory(tmpdir1))
- end)
- local tmpname2 = fn.tempname()
- local tmpdir2 = fn.fnamemodify(tmpname2, ':h')
- neq(tmpdir1, tmpdir2)
- end
- -- Your antivirus hates you...
- rm_tmpdir()
- assert_log('tempdir disappeared', testlog, 100)
- fn.tempname()
- fn.tempname()
- fn.tempname()
- eq('', api.nvim_get_vvar('errmsg'))
- rm_tmpdir()
- fn.tempname()
- fn.tempname()
- fn.tempname()
- eq('E5431: tempdir disappeared (2 times)', api.nvim_get_vvar('errmsg'))
- rm_tmpdir()
- eq('E5431: tempdir disappeared (3 times)', api.nvim_get_vvar('errmsg'))
- end)
- end)
|