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)