hephaestus/heph.nvim/tests/e2e/runner.lua
Erich Blume ee865e5635
Some checks failed
Build / validate (pull_request) Failing after 4s
heph.nvim: RPC client + buffer editing + wiki-links + journal (slice 11a)
The primary surface begins (tech-spec §8): a Neovim plugin that is a thin
client of the local hephd over its unix-socket JSON-RPC.

- node.resolve {title} → Node|null (heph-core Store + dispatch): exact,
  owner-scoped, non-tombstoned alias-then-title match — the same mapping that
  materializes wiki links, so follow-link jumps to the node the stored link
  points at (never fuzzy search). Unit + rpc_socket integration tests.
- heph.nvim/: vim.uv unix-socket JSON-RPC client (blocking call via vim.wait,
  id-demuxed, partial-line buffered, luanil so JSON null → Lua nil; isolated
  Sessions for tests). Buffer-backed nodes (heph://node/<id>, acwrite;
  BufReadCmd→node.get / BufWriteCmd→node.update, whole-buffer body round-trips
  exactly through the CRDT). [[wiki-link]] follow on <CR>. Daily journal.
  :Heph command surface + completion.
- Headless e2e (§9): a self-contained busted-style runner (tests/e2e/runner.lua)
  — no external plugins, no network, deterministic CI exit codes. Specs: journal
  round-trip, follow-link (+ unresolved no-op), link-two-docs/backlink.
  `make -C heph.nvim test` builds hephd and runs it.

Docs: heph-nvim reference card, §14 tracker (11a done; 11b/11c/11d queued),
changelog fragment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:33:29 -07:00

140 lines
4 KiB
Lua

--- A tiny, dependency-free busted-compatible test runner (tech-spec §9 sanctions
--- "drive nvim ... from the test runner"). Provides the `describe`/`it`/
--- `before_each`/`after_each` globals and a luassert-style `assert` table, then
--- runs `*_spec.lua` files in headless Neovim with proper exit codes — no
--- external plugins, no network, nothing vendored.
local M = {}
-- Registration state (module-local; the installed globals close over these).
local cases = {}
local scope_stack = {}
local function full_name(leaf)
local parts = {}
for _, s in ipairs(scope_stack) do
if s.name then
parts[#parts + 1] = s.name
end
end
parts[#parts + 1] = leaf
return table.concat(parts, " ")
end
--- Install the spec globals. `assert` becomes a callable luassert-ish table:
--- `assert(cond, msg)` still works (so harness code using the builtin is fine),
--- plus `assert.are.equal`, `assert.is_true/is_false/is_truthy/is_falsy`.
function M.install_globals()
_G.describe = function(name, fn)
table.insert(scope_stack, { name = name, befores = {}, afters = {} })
fn()
table.remove(scope_stack)
end
_G.before_each = function(fn)
table.insert(scope_stack[#scope_stack].befores, fn)
end
_G.after_each = function(fn)
table.insert(scope_stack[#scope_stack].afters, fn)
end
_G.it = function(name, fn)
-- Snapshot the active before/after chain (outermost-first for befores,
-- innermost-first for afters), as busted runs them per-test.
local befores, afters = {}, {}
for _, s in ipairs(scope_stack) do
for _, b in ipairs(s.befores) do
befores[#befores + 1] = b
end
end
for i = #scope_stack, 1, -1 do
for _, a in ipairs(scope_stack[i].afters) do
afters[#afters + 1] = a
end
end
table.insert(cases, { name = full_name(name), fn = fn, befores = befores, afters = afters })
end
local function fail(msg, level)
error(msg, (level or 1) + 1)
end
local A = setmetatable({}, {
__call = function(_, cond, msg)
if not cond then
fail(msg or "assertion failed")
end
return cond
end,
})
local function eq(a, b, msg)
if a ~= b then
fail(msg or string.format("expected %s, got %s", vim.inspect(b), vim.inspect(a)))
end
end
A.are = { equal = eq, equals = eq }
A.equals = eq
A.equal = eq
A.is_true = function(x, msg)
if x ~= true then
fail(msg or ("expected true, got " .. vim.inspect(x)))
end
end
A.is_false = function(x, msg)
if x ~= false then
fail(msg or ("expected false, got " .. vim.inspect(x)))
end
end
A.is_truthy = function(x, msg)
if not x then
fail(msg or ("expected truthy, got " .. vim.inspect(x)))
end
end
A.is_falsy = function(x, msg)
if x then
fail(msg or ("expected falsy, got " .. vim.inspect(x)))
end
end
_G.assert = A
end
--- Run each spec file, returning the number of failed tests. Prints TAP-ish
--- lines so failures are obvious in CI logs.
function M.run_files(files)
local passed, failed = 0, 0
for _, file in ipairs(files) do
cases = {}
scope_stack = {}
local loaded, lerr = pcall(dofile, file)
if not loaded then
failed = failed + 1
print("not ok - load " .. file)
print(" " .. tostring(lerr):gsub("\n", "\n "))
end
for _, c in ipairs(cases) do
local ok, err = true, nil
for _, b in ipairs(c.befores) do
local bok, berr = pcall(b)
if not bok then
ok, err = false, berr
break
end
end
if ok then
ok, err = pcall(c.fn)
end
for _, a in ipairs(c.afters) do
pcall(a) -- teardown always runs, even after a failure
end
if ok then
passed = passed + 1
print("ok - " .. c.name)
else
failed = failed + 1
print("not ok - " .. c.name)
print(" " .. tostring(err):gsub("\n", "\n "))
end
end
end
print(string.format("\n%d passed, %d failed", passed, failed))
return failed
end
return M