neovim

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

watch_spec.lua (5023B)


      1 local t = require('test.testutil')
      2 local n = require('test.functional.testnvim')()
      3 
      4 local eq = t.eq
      5 local exec_lua = n.exec_lua
      6 local clear = n.clear
      7 local is_ci = t.is_ci
      8 local is_os = t.is_os
      9 local skip = t.skip
     10 
     11 -- Create a file via a rename to avoid multiple
     12 -- events which can happen with some backends on some platforms
     13 local function touch(path)
     14  local tmp = t.tmpname()
     15  assert(vim.uv.fs_rename(tmp, path))
     16 end
     17 
     18 describe('vim._watch', function()
     19  before_each(function()
     20    clear()
     21  end)
     22 
     23  local function run(watchfunc)
     24    -- Monkey-patches vim.notify_once so we can "spy" on it.
     25    local function spy_notify_once()
     26      exec_lua [[
     27        _G.__notify_once_msgs = {}
     28        vim.notify_once = (function(overridden)
     29          return function(msg, level, opts)
     30            table.insert(_G.__notify_once_msgs, msg)
     31            return overridden(msg, level, opts)
     32          end
     33        end)(vim.notify_once)
     34      ]]
     35    end
     36 
     37    local function last_notify_once_msg()
     38      return exec_lua 'return _G.__notify_once_msgs[#_G.__notify_once_msgs]'
     39    end
     40 
     41    local function do_watch(root_dir, watchfunc_)
     42      exec_lua(
     43        [[
     44          local root_dir, watchfunc = ...
     45 
     46          _G.events = {}
     47 
     48          _G.stop_watch = vim._watch[watchfunc](root_dir, {
     49            debounce = 100,
     50            include_pattern = vim.lpeg.P(root_dir) * vim.lpeg.P("/file") ^ -1,
     51            exclude_pattern = vim.lpeg.P(root_dir .. '/file.unwatched'),
     52          }, function(path, change_type)
     53            table.insert(_G.events, { path = path, change_type = change_type })
     54          end)
     55      ]],
     56        root_dir,
     57        watchfunc_
     58      )
     59    end
     60 
     61    it(watchfunc .. '() ignores nonexistent paths', function()
     62      if watchfunc == 'inotify' then
     63        skip(n.fn.executable('inotifywait') == 0, 'inotifywait not found')
     64        skip(is_os('bsd'), 'inotifywait on bsd CI seems to expect path to exist?')
     65      end
     66 
     67      local msg = ('watch.%s: ENOENT: no such file or directory'):format(watchfunc)
     68 
     69      spy_notify_once()
     70      do_watch('/i am /very/funny.go', watchfunc)
     71 
     72      if watchfunc ~= 'inotify' then -- watch.inotify() doesn't (currently) call vim.notify_once.
     73        t.retry(nil, 2000, function()
     74          t.eq(msg, last_notify_once_msg())
     75        end)
     76      end
     77      eq(0, exec_lua [[return #_G.events]])
     78 
     79      exec_lua [[_G.stop_watch()]]
     80    end)
     81 
     82    it(watchfunc .. '() detects file changes', function()
     83      if watchfunc == 'inotify' then
     84        skip(is_os('win'), 'not supported on windows')
     85        skip(is_os('mac'), 'flaky test on mac')
     86        skip(not is_ci() and n.fn.executable('inotifywait') == 0, 'inotifywait not found')
     87      end
     88 
     89      -- Note: because this is not `elseif`, BSD is skipped for *all* cases...?
     90      if watchfunc == 'watch' then
     91        skip(is_os('mac'), 'flaky test on mac')
     92        skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38')
     93      elseif watchfunc == 'watchdirs' and is_os('mac') then
     94        skip(true, 'weird failure since macOS 14 CI, see bbf208784ca279178ba0075b60d3e9c80f11da7a')
     95      else
     96        skip(
     97          is_os('bsd'),
     98          'kqueue only reports events on watched folder itself, not contained files #26110'
     99        )
    100      end
    101 
    102      local expected_events = 0
    103      --- Waits for a new event, or fails if no events are triggered.
    104      local function wait_for_event()
    105        expected_events = expected_events + 1
    106        exec_lua(
    107          [[
    108            local expected_events = ...
    109            assert(
    110              vim.wait(3000, function()
    111                return #_G.events == expected_events
    112              end),
    113              string.format(
    114                'Timed out waiting for expected event no. %d. Current events seen so far: %s',
    115                expected_events,
    116                vim.inspect(events)
    117              )
    118            )
    119        ]],
    120          expected_events
    121        )
    122      end
    123 
    124      local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX')
    125      local unwatched_path = root_dir .. '/file.unwatched'
    126      local watched_path = root_dir .. '/file'
    127 
    128      do_watch(root_dir, watchfunc)
    129 
    130      if watchfunc ~= 'watch' then
    131        vim.uv.sleep(200)
    132      end
    133 
    134      touch(watched_path)
    135      touch(unwatched_path)
    136      wait_for_event()
    137 
    138      os.remove(watched_path)
    139      os.remove(unwatched_path)
    140      wait_for_event()
    141 
    142      exec_lua [[_G.stop_watch()]]
    143      -- No events should come through anymore
    144 
    145      vim.uv.sleep(100)
    146      touch(watched_path)
    147      vim.uv.sleep(100)
    148      os.remove(watched_path)
    149      vim.uv.sleep(100)
    150 
    151      eq({
    152        {
    153          change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
    154          path = root_dir .. '/file',
    155        },
    156        {
    157          change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
    158          path = root_dir .. '/file',
    159        },
    160      }, exec_lua [[return _G.events]])
    161    end)
    162  end
    163 
    164  run('watch')
    165  run('watchdirs')
    166  run('inotify')
    167 end)