watch_spec.lua 5.0 KB

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