Skip to content

Instantly share code, notes, and snippets.

@hoelzro
Created February 21, 2026 13:39
Show Gist options
  • Select an option

  • Save hoelzro/e71211be671f1a593a4d507bc56daacc to your computer and use it in GitHub Desktop.

Select an option

Save hoelzro/e71211be671f1a593a4d507bc56daacc to your computer and use it in GitHub Desktop.
local inotify = require 'inotify'
local CHARSET = 'abcdefghijklmnopqrstuvwxyz0123456789'
local charset_idx = 0
local function next_name(len)
charset_idx = charset_idx + 1
assert(charset_idx <= #CHARSET)
return string.rep(string.sub(CHARSET, charset_idx, charset_idx), len)
end
local function make_tmpdir()
local p = assert(io.popen 'mktemp -d')
local path = p:read '*l'
p:close()
return path
end
local function collect_via_read(handle)
local got = {}
while true do
local events = handle:read()
if not events or #events == 0 then
break
end
for _, ev in ipairs(events) do
if ev.name then
got[ev.name] = true
end
end
end
os.execute 'sleep 1'
return got
end
local function compare_sets(expected, received)
local missing = {}
local extra = {}
for name in pairs(expected) do
if not received[name] then
missing[#missing + 1] = name
end
end
for name in pairs(received) do
if not expected[name] then
extra[#extra + 1] = name
end
end
return missing, extra
end
local function table_count(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
local debug = print
-- function debug() end
local function run_round(handle, tmpdir, name_lens)
local expected = {}
for i, len in ipairs(name_lens) do
local name = next_name(len)
expected[name] = true
debug('creating', name)
local f = assert(io.open(tmpdir .. '/' .. name, 'w'))
f:close()
end
-- Events are generated synchronously by the kernel before open() returns,
-- but give the compat layer a moment just in case.
os.execute 'sleep 1'
local got = collect_via_read(handle)
debug('got ', table_count(got), 'events')
debug('expected ', table_count(expected), 'events')
local missing, extra = compare_sets(expected, got)
-- interestingly enough, if I skip this - it works!
for name in pairs(expected) do
os.remove(tmpdir .. '/' .. name)
end
return missing, extra
end
local function report_fail(file_count, missing, extra)
io.stderr:write(string.format(
'FAIL (files=%d)\n',
file_count
))
if #missing > 0 then
io.stderr:write(' missing events:\n')
for _, name in ipairs(missing) do
io.stderr:write(string.format(' %s (len=%d)\n', name, #name))
end
end
if #extra > 0 then
io.stderr:write(' unexpected events:\n')
for _, name in ipairs(extra) do
io.stderr:write(string.format(' %s (len=%d)\n', name, #name))
end
end
end
local failures = 0
local function test_round(handle, tmpdir, name_lens)
local missing, extra = run_round(handle, tmpdir, name_lens)
if #missing > 0 or #extra > 0 then
failures = failures + 1
report_fail(#name_lens, missing, extra)
end
end
local tmpdir = make_tmpdir()
local handle = assert(inotify.init {
blocking = false,
})
local wd = assert(handle:addwatch(tmpdir, inotify.IN_CREATE))
for _ = 1, 2 do
local name_lens = {}
for i = 1, 18 do
name_lens[i] = 39
end
test_round(handle, tmpdir, name_lens)
end
os.exit(failures == 0 and 0 or 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment