generated from eblume/project-template
Some checks failed
Build / validate (pull_request) Failing after 4s
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>
140 lines
4 KiB
Lua
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
|