Files
neovim/test/functional/harness/harness_spec.lua
Lewis Russell 55f9c2136e test: replace busted with local harness
Replace the busted-based Lua test runner with a repo-local harness.

The new harness runs spec files directly under `nvim -ll`, ships its own
reporter and lightweight `luassert` shim, and keeps the helper/preload
flow used by the functional and unit test suites.

Keep the file boundary model shallow and busted-like by restoring `_G`,
`package.loaded`, `package.preload`, `arg`, and the process environment
between files, without carrying extra reset APIs or custom assertion
machinery.

Update the build and test entrypoints to use the new runner, add
black-box coverage for the harness itself, and drop the bundled
busted/luacheck dependency path.

AI-assisted: Codex
2026-04-15 12:09:25 +01:00

1383 lines
37 KiB
Lua

-- Black-box tests for the local Lua harness itself. These spawn a separate
-- low-level Nvim process instead of driving an embedded instance via testnvim.
local t = require('test.testutil')
local uv = vim.uv
local eq = t.eq
local root = t.paths.test_source_path
local build_dir = t.paths.test_build_dir
local nvim_prog = build_dir .. '/bin/nvim'
local runner = root .. '/test/runner.lua'
---@param path string
local function mkdir(path)
assert(t.mkdir(path), ('failed to create directory: %s'):format(path))
end
---@param suite_files table<string, string>
---@return string
local function write_suite(suite_files)
local dir = t.tmpname(false)
mkdir(dir)
for name, contents in pairs(suite_files) do
t.write_file(dir .. '/' .. name, contents)
end
return dir
end
---@param overrides table<string, string?>
---@return string[]
local function make_env(overrides)
local env = uv.os_environ()
for k, v in pairs(overrides) do
env[k] = v
end
local items = {}
for k, v in pairs(env) do
if v ~= nil then
items[#items + 1] = k .. '=' .. v
end
end
return items
end
---@param suite_dir string
---@param extra_args? string[]
---@return integer, string
local function run_harness(suite_dir, extra_args)
local env_root = t.tmpname(false)
mkdir(env_root)
mkdir(env_root .. '/config')
mkdir(env_root .. '/share')
mkdir(env_root .. '/state')
mkdir(env_root .. '/tmp')
local stdout = assert(uv.new_pipe(false))
local stderr = assert(uv.new_pipe(false))
local out = {}
local err = {}
local exit_code --- @type integer?
local args = {
'-ll',
runner,
'-v',
'--lpath=' .. build_dir .. '/?.lua',
'--lpath=' .. root .. '/src/?.lua',
'--lpath=' .. root .. '/runtime/lua/?.lua',
'--lpath=' .. suite_dir .. '/?.lua',
'--lpath=?.lua',
}
for _, arg in ipairs(extra_args or {}) do
args[#args + 1] = arg
end
args[#args + 1] = suite_dir
local handle = assert(uv.spawn(nvim_prog, {
args = args,
env = make_env({
NVIM_TEST = '1',
TEST_COLORS = '0',
VIMRUNTIME = root .. '/runtime',
XDG_CONFIG_HOME = env_root .. '/config',
XDG_DATA_HOME = env_root .. '/share',
XDG_STATE_HOME = env_root .. '/state',
NVIM_RPLUGIN_MANIFEST = env_root .. '/rplugin_manifest',
NVIM_LOG_FILE = env_root .. '/nvim.log',
TMPDIR = env_root .. '/tmp',
SYSTEM_NAME = os.getenv('SYSTEM_NAME') or uv.os_uname().sysname,
SHELL = os.getenv('SHELL') or 'sh',
}),
stdio = { nil, stdout, stderr },
hide = true,
}, function(code, _signal)
exit_code = code
end))
local function read_all(pipe, chunks)
pipe:read_start(function(read_err, chunk)
assert(not read_err, read_err)
if chunk == nil then
pipe:read_stop()
pipe:close()
else
chunks[#chunks + 1] = chunk
end
end)
end
read_all(stdout, out)
read_all(stderr, err)
while exit_code == nil or not stdout:is_closing() or not stderr:is_closing() do
uv.run('once')
end
handle:close()
while not handle:is_closing() do
uv.run('once')
end
return exit_code, table.concat(out) .. table.concat(err)
end
---@param suite_dir string
---@param extra_args? string[]
local function assert_harness_passes(suite_dir, extra_args)
local code, output = run_harness(suite_dir, extra_args)
eq(0, code)
eq(true, output:find('PASSED', 1, true) ~= nil)
end
describe('test harness', function()
it('restores package.preload between files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
package.preload.leak_mod = function()
return 'leak'
end
describe('one', function()
it('defines a preload entry', function()
assert.Equal('leak', require('leak_mod'))
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('does not see the preload entry from another file', function()
local ok = pcall(require, 'leak_mod')
assert.False(ok)
end)
end)
]],
})
assert_harness_passes(suite_dir)
end)
it('restores package.loaded between files', function()
local suite_dir = write_suite({
['loaded_mod.lua'] = [[
return { source = 'file' }
]],
['one_spec.lua'] = [[
describe('one', function()
it('mutates package.loaded', function()
assert.Equal('file', require('loaded_mod').source)
package.loaded.loaded_mod = 'leak'
assert.Equal('leak', require('loaded_mod'))
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('does not see package.loaded leaks from another file', function()
local mod = require('loaded_mod')
assert.Equal('file', mod.source)
end)
end)
]],
})
assert_harness_passes(suite_dir)
end)
it('restores environment variables between files', function()
local env_name = '__NVIM_HARNESS_TEST_ENV__'
local suite_dir = write_suite({
['one_spec.lua'] = string.format(
[[
describe('one', function()
it('mutates the environment', function()
vim.uv.os_setenv(%q, 'leak')
assert.Equal('leak', vim.uv.os_getenv(%q))
end)
end)
]],
env_name,
env_name
),
['two_spec.lua'] = string.format(
[[
describe('two', function()
it('does not see environment leaks from another file', function()
assert.Equal(nil, vim.uv.os_getenv(%q))
end)
end)
]],
env_name
),
})
assert_harness_passes(suite_dir)
end)
it('restores globals between files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('mutates a global', function()
_G.__harness_leak = 'leak'
assert.Equal('leak', _G.__harness_leak)
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('does not see global leaks from another file', function()
assert.Equal(nil, _G.__harness_leak)
end)
end)
]],
})
assert_harness_passes(suite_dir)
end)
it('restores arg between files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('mutates arg', function()
_G.arg.__leak = 'leak'
assert.Equal('leak', _G.arg.__leak)
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('does not see arg leaks from another file', function()
assert.Equal(nil, _G.arg.__leak)
end)
end)
]],
})
assert_harness_passes(suite_dir)
end)
it('restores helper-provided globals to their baseline between files', function()
local suite_dir = write_suite({
['helper.lua'] = [[
_G.helper_value = 'baseline'
]],
['one_spec.lua'] = [[
describe('one', function()
it('mutates a helper-provided global', function()
assert.Equal('baseline', _G.helper_value)
_G.helper_value = 'leak'
assert.Equal('leak', _G.helper_value)
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('restores the helper baseline between files', function()
assert.Equal('baseline', _G.helper_value)
end)
end)
]],
})
assert_harness_passes(suite_dir, {
'--helper=' .. suite_dir .. '/helper.lua',
})
end)
it('restores helper-provided globals shallowly between files', function()
local suite_dir = write_suite({
['helper.lua'] = [[
_G.helper_value = { nested = 'baseline' }
]],
['one_spec.lua'] = [[
describe('one', function()
it('mutates nested helper state in place', function()
assert.Equal('baseline', _G.helper_value.nested)
_G.helper_value.nested = 'leak'
assert.Equal('leak', _G.helper_value.nested)
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('keeps the same nested helper table', function()
assert.Equal('leak', _G.helper_value.nested)
end)
end)
]],
})
assert_harness_passes(suite_dir, {
'--helper=' .. suite_dir .. '/helper.lua',
})
end)
it('keeps helper-loaded modules across files', function()
local suite_dir = write_suite({
['helper_mod.lua'] = [[
_G.helper_module_loads = (_G.helper_module_loads or 0) + 1
return { loads = _G.helper_module_loads }
]],
['helper.lua'] = [[
require('helper_mod')
]],
['one_spec.lua'] = [[
describe('one', function()
it('uses the helper-loaded module', function()
assert.Equal(1, require('helper_mod').loads)
end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('does not reload the helper module', function()
assert.Equal(1, require('helper_mod').loads)
end)
end)
]],
})
assert_harness_passes(suite_dir, {
'--helper=' .. suite_dir .. '/helper.lua',
})
end)
it('rejects helpers that register hooks', function()
local suite_dir = write_suite({
['helper.lua'] = [[
before_each(function() end)
]],
['one_spec.lua'] = [[
describe('real', function()
it('passes', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--helper=' .. suite_dir .. '/helper.lua',
})
eq(1, code)
eq(true, not not output:find("attempt to call global 'before_each'", 1, true), output)
end)
it('rejects helpers that define suites or tests', function()
local suite_dir = write_suite({
['helper.lua'] = [[
describe('helper suite', function()
it('should fail', function() end)
end)
]],
['one_spec.lua'] = [[
describe('real', function()
it('passes', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--helper=' .. suite_dir .. '/helper.lua',
})
eq(1, code)
eq(true, not not output:find("attempt to call global 'describe'", 1, true), output)
end)
it('deduplicates suite-end callbacks registered from the same module', function()
local marker = t.tmpname(false)
local suite_dir = write_suite({
['cbmod.lua'] = string.format(
[[
local harness = require('test.harness')
harness.on_suite_end(function()
local file = assert(io.open(%q, 'ab'))
file:write('hit\n')
file:close()
end)
return true
]],
marker
),
['one_spec.lua'] = [[
require('cbmod')
describe('one', function()
it('works', function()
assert.True(true)
end)
end)
]],
['two_spec.lua'] = [[
require('cbmod')
describe('two', function()
it('works', function()
assert.True(true)
end)
end)
]],
})
assert_harness_passes(suite_dir)
eq('hit\n', t.read_file(marker))
end)
it(
'keeps distinct suite-end callbacks from long paths with the same truncated short_src tail',
function()
local marker = t.tmpname(false)
local suite_dir = t.tmpname(false)
local left = suite_dir
.. '/'
.. string.rep('L', 100)
.. '/common/common/common/common/common/same_suffix'
local right = suite_dir
.. '/'
.. string.rep('R', 100)
.. '/common/common/common/common/common/same_suffix'
mkdir(suite_dir)
for _, path in ipairs({ left, right }) do
local parents = {}
local dir = path
while dir ~= suite_dir and not uv.fs_stat(dir) do
table.insert(parents, 1, dir)
dir = vim.fs.dirname(dir)
end
for _, parent in ipairs(parents) do
if not uv.fs_stat(parent) then
mkdir(parent)
end
end
end
t.write_file(
left .. '/same_spec.lua',
string.format(
[[
local harness = require('test.harness')
harness.on_suite_end(function()
local file = assert(io.open(%q, 'ab'))
file:write('left\n')
file:close()
end)
describe('left', function()
it('works', function() end)
end)
]],
marker
)
)
t.write_file(
right .. '/same_spec.lua',
string.format(
[[
local harness = require('test.harness')
harness.on_suite_end(function()
local file = assert(io.open(%q, 'ab'))
file:write('right\n')
file:close()
end)
describe('right', function()
it('works', function() end)
end)
]],
marker
)
)
assert_harness_passes(suite_dir)
eq('left\nright\n', t.read_file(marker))
end
)
it('reports suite-end callback failures without crashing the runner', function()
local suite_dir = write_suite({
['cbmod.lua'] = [[
local harness = require('test.harness')
harness.on_suite_end(function()
error('boom from suite_end')
end)
return true
]],
['one_spec.lua'] = [[
require('cbmod')
describe('one', function()
it('works', function()
assert.True(true)
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('[suite_end 1]', 1, true))
eq(true, not not output:find('suite_end', 1, true))
eq(true, not not output:find('boom from suite_end', 1, true))
eq(true, not not output:find('ERROR ', 1, true))
eq(false, not not output:find('FAILED ', 1, true))
eq(true, not not output:find('1 test from 1 test file ran.', 1, true))
eq(true, not not output:find('Global test environment teardown.', 1, true))
end)
it('reports wrapped suite-end callback failures at the callback definition line', function()
local suite_dir = write_suite({
['wrapper.lua'] = [[
local M = {}
function M.fail()
error('boom from wrapper')
end
return M
]],
['cbmod.lua'] = [[
local harness = require('test.harness')
local wrapper = require('wrapper')
harness.on_suite_end(function()
wrapper.fail()
end)
return true
]],
['one_spec.lua'] = [[
require('cbmod')
describe('one', function()
it('works', function()
assert.True(true)
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('cbmod.lua @ 4: [suite_end 1]', 1, true), output)
eq(false, not not output:find('wrapper.lua @ 4:', 1, true), output)
end)
it('reloads helper suite-end callbacks between repeats', function()
local marker = t.tmpname(false)
local suite_dir = write_suite({
['helper.lua'] = string.format(
[[
local harness = require('test.harness')
local hits = 0
harness.on_suite_end(function()
hits = hits + 1
local file = assert(io.open(%q, 'ab'))
file:write(hits .. '\n')
file:close()
end)
]],
marker
),
['one_spec.lua'] = [[
describe('one', function()
it('works', function()
assert.True(true)
end)
end)
]],
})
assert_harness_passes(suite_dir, {
'--helper=' .. suite_dir .. '/helper.lua',
'--repeat=2',
})
eq('1\n1\n', t.read_file(marker))
end)
it('restores process state before each repeat', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
local harness = require('test.harness')
harness.on_suite_end(function()
_G.__repeat_leak = 'leak'
end)
describe('one', function()
it('starts clean', function()
assert.Equal(nil, _G.__repeat_leak)
end)
end)
]],
})
assert_harness_passes(suite_dir, {
'--repeat=2',
})
end)
it('filters tests by tags across files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('fast #fast', function() end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('slow #slow', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--tags=fast',
})
eq(0, code)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
eq(true, not not output:find('one fast #fast', 1, true), output)
eq(false, not not output:find('two slow #slow', 1, true), output)
end)
it('filters tests by suite tags across files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one #fast', function()
it('works', function() end)
end)
]],
['two_spec.lua'] = [[
describe('two #slow', function()
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--tags=fast',
})
eq(0, code)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
eq(true, not not output:find('one #fast works', 1, true), output)
eq(false, not not output:find('two #slow works', 1, true), output)
end)
it('filters tests by name across files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('chosen', function() end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('skipped', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--filter=chosen',
})
eq(0, code)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
eq(true, not not output:find('one chosen', 1, true), output)
eq(false, not not output:find('two skipped', 1, true), output)
end)
it('filters tests by suite name across files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('chosen suite', function()
it('works', function() end)
end)
]],
['two_spec.lua'] = [[
describe('skipped suite', function()
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--filter',
'chosen suite',
})
eq(0, code)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
eq(true, not not output:find('chosen suite works', 1, true), output)
eq(false, not not output:find('skipped suite works', 1, true), output)
end)
it('does not keep suite-end callbacks from filtered-out files', function()
local marker = t.tmpname(false)
local suite_dir = write_suite({
['one_spec.lua'] = string.format(
[[
local harness = require('test.harness')
harness.on_suite_end(function()
local file = assert(io.open(%q, 'ab'))
file:write('filtered\n')
file:close()
end)
describe('one', function()
it('skipped', function()
assert.True(true)
end)
end)
]],
marker
),
['two_spec.lua'] = [[
describe('two', function()
it('chosen', function()
assert.True(true)
end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--filter=chosen',
})
eq(0, code)
eq(nil, uv.fs_stat(marker))
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
end)
it('filters tests out by name across files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('chosen', function() end)
end)
]],
['two_spec.lua'] = [[
describe('two', function()
it('skipped', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--filter-out=skipped',
})
eq(0, code)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
eq(true, not not output:find('one chosen', 1, true), output)
eq(false, not not output:find('two skipped', 1, true), output)
end)
it('filters tests out by suite name across files', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('chosen suite', function()
it('works', function() end)
end)
]],
['two_spec.lua'] = [[
describe('skipped suite', function()
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--filter-out',
'skipped suite',
})
eq(0, code)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
eq(true, not not output:find('chosen suite works', 1, true), output)
eq(false, not not output:find('skipped suite works', 1, true), output)
end)
it('reports when filters exclude all tests', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('skipped', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, {
'--filter-out=.',
})
eq(1, code)
eq(true, not not output:find('No tests matched the current selection.', 1, true), output)
eq(false, not not output:find('Running tests from', 1, true), output)
end)
it('reports malformed filter patterns clearly before selection runs', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('works', function() end)
end)
]],
})
for _, case in ipairs({
{ '--filter=[', 'invalid value for --filter: malformed pattern' },
{ '--filter-out=[', 'invalid value for --filter-out: malformed pattern' },
}) do
local code, output = run_harness(suite_dir, { case[1] })
eq(1, code)
eq(true, not not output:find(case[2], 1, true), output)
eq(false, not not output:find('test_selected', 1, true), output)
eq(false, not not output:find('Running tests from', 1, true), output)
eq(false, not not output:find('test harness failed with exit code', 1, true), output)
end
end)
it('skips unreadable test directories and keeps running readable files', function()
if t.is_os('win') then
pending('N/A: permission denied directory scan depends on POSIX chmod')
end
local suite_dir = t.tmpname(false)
mkdir(suite_dir)
t.write_file(
suite_dir .. '/one_spec.lua',
[[
describe('one', function()
it('works', function() end)
end)
]]
)
local blocked = suite_dir .. '/blocked'
mkdir(blocked)
t.write_file(
blocked .. '/two_spec.lua',
[[
describe('two', function()
it('is hidden behind permissions', function() end)
end)
]]
)
finally(function()
assert(uv.fs_chmod(blocked, 448))
end)
assert(uv.fs_chmod(blocked, 0))
local code, output = run_harness(suite_dir)
eq(0, code)
eq(true, not not output:find('Running tests from', 1, true), output)
eq(true, not not output:find('one works', 1, true), output)
eq(false, not not output:find('two is hidden behind permissions', 1, true), output)
eq(true, not not output:find('1 test from 1 test file ran.', 1, true), output)
end)
it('reports missing test paths clearly before loading suites', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('works', function() end)
end)
]],
})
local missing = suite_dir .. '/missing'
local code, output = run_harness(suite_dir, { missing })
eq(1, code)
eq(true, not not output:find('test path not found: ' .. missing, 1, true), output)
eq(false, not not output:find('collect_test_files', 1, true), output)
eq(false, not not output:find('Running tests from', 1, true), output)
eq(false, not not output:find('test harness failed with exit code', 1, true), output)
end)
it('reports missing helpers clearly before running suites', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('works', function() end)
end)
]],
})
local missing = suite_dir .. '/missing.lua'
local code, output = run_harness(suite_dir, {
'--helper=' .. missing,
})
eq(1, code)
eq(true, not not output:find('cannot open ' .. missing, 1, true), output)
eq(false, not not output:find('load_helper', 1, true), output)
eq(false, not not output:find('Running tests from', 1, true), output)
eq(false, not not output:find('test harness failed with exit code', 1, true), output)
end)
it('reports empty value options before running suites', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('works', function() end)
end)
]],
})
for _, case in ipairs({
{ '--helper=', 'missing value for --helper' },
{ '--summary-file=', 'missing value for --summary-file' },
}) do
local code, output = run_harness(suite_dir, {
case[1],
})
eq(1, code)
eq(true, not not output:find(case[2], 1, true), output)
eq(false, not not output:find('open_summary_file', 1, true), output)
eq(false, not not output:find('Running tests from', 1, true), output)
eq(false, not not output:find('test harness failed with exit code', 1, true), output)
end
end)
it('treats the next argv item as the value even when it starts with dashes', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir, { '--filter', '--verbose' })
eq(1, code)
eq(true, not not output:find('No tests matched the current selection.', 1, true), output)
eq(false, not not output:find('missing value for --filter', 1, true), output)
code, output = run_harness(suite_dir, { '--repeat', '-1' })
eq(1, code)
eq(true, not not output:find('invalid value for --repeat: -1', 1, true), output)
eq(false, not not output:find('missing value for --repeat', 1, true), output)
end)
it('reports test-body errors as failures', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('fails', function()
error('boom from test')
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('boom from test', 1, true))
eq(true, not not output:find('FAILED ', 1, true))
eq(false, not not output:find('ERROR ', 1, true))
end)
it('reports test-body failures at the failing line', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('fails', function()
error('boom from test')
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 3: one fails', 1, true), output)
end)
it('ignores fake trace lines embedded in multiline error messages', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('fake trace', function()
error('boom\n' .. debug.getinfo(1, 'S').source:sub(2) .. ':999: fake trace', 0)
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 3: one fake trace', 1, true), output)
eq(false, not not output:find('one_spec.lua @ 999: one fake trace', 1, true), output)
end)
it('reports wrapped assertion failures at the test definition line', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('equal fail', function()
assert.Equal(1, 2)
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 2: one equal fail', 1, true), output)
eq(false, not not output:find(root .. '/test/assert.lua @', 1, true), output)
end)
it('reports local wrapper failures at the test definition line', function()
local suite_dir = write_suite({
['wrapper.lua'] = [[
local M = {}
function M.fail()
error('boom from wrapper')
end
return M
]],
['one_spec.lua'] = [[
local wrapper = require('wrapper')
describe('one', function()
it('wrapper fail', function()
wrapper.fail()
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 4: one wrapper fail', 1, true), output)
eq(false, not not output:find('wrapper.lua @ 4:', 1, true), output)
end)
it('reports failing finally cleanup at the cleanup line', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
it('finfail', function()
finally(function()
error('boom fin')
end)
pending('later')
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 4: one finfail', 1, true), output)
end)
it('rejects finally in setup hooks', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
setup(function()
finally(function() end)
end)
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 2: one [setup]', 1, true), output)
eq(
true,
not not output:find('finally() must be called while a test body is running', 1, true),
output
)
end)
it('rejects finally in teardown hooks', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
teardown(function()
finally(function() end)
end)
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 2: one [teardown]', 1, true), output)
eq(
true,
not not output:find('finally() must be called while a test body is running', 1, true),
output
)
end)
it('reports failing after_each cleanup at the cleanup line', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('one', function()
after_each(function()
error('boom after')
end)
it('afterfail', function()
pending('later')
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 3: one afterfail', 1, true), output)
end)
it('reports cross-file after_each wrapper failures at the hook definition line', function()
local suite_dir = write_suite({
['wrapper.lua'] = [[
local M = {}
function M.fail()
error('boom from wrapper')
end
return M
]],
['one_spec.lua'] = [[
local wrapper = require('wrapper')
describe('one', function()
after_each(function()
wrapper.fail()
end)
it('afterwrap', function()
assert.True(true)
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 4: one afterwrap', 1, true), output)
eq(false, not not output:find('one_spec.lua @ 8: one afterwrap', 1, true), output)
eq(false, not not output:find('wrapper.lua @ 4:', 1, true), output)
end)
it(
'reports pending cross-file after_each wrapper failures at the hook definition line',
function()
local suite_dir = write_suite({
['wrapper.lua'] = [[
local M = {}
function M.fail()
error('boom from wrapper')
end
return M
]],
['one_spec.lua'] = [[
local wrapper = require('wrapper')
describe('one', function()
after_each(function()
wrapper.fail()
end)
it('afterwrap pending', function()
pending('later')
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 4: one afterwrap pending', 1, true), output)
eq(false, not not output:find('one_spec.lua @ 8: one afterwrap pending', 1, true), output)
eq(false, not not output:find('wrapper.lua @ 4:', 1, true), output)
end
)
it('reports cross-file finally wrapper failures at the cleanup definition line', function()
local suite_dir = write_suite({
['wrapper.lua'] = [[
local M = {}
function M.fail()
error('boom from wrapper')
end
return M
]],
['one_spec.lua'] = [[
local wrapper = require('wrapper')
describe('one', function()
it('finwrap', function()
finally(function()
wrapper.fail()
end)
end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 5: one finwrap', 1, true), output)
eq(false, not not output:find('one_spec.lua @ 4: one finwrap', 1, true), output)
eq(false, not not output:find('wrapper.lua @ 4:', 1, true), output)
end)
it('does not count synthetic hook failures as executed tests', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('trace', function()
setup(function()
error('boom setup')
end)
it('passes', function() end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('0 tests from 1 test file ran.', 1, true))
eq(true, not not output:find('1 ERROR', 1, true))
end)
it('reports setup failures at the hook definition site', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
describe('trace', function()
setup(function()
error('boom setup')
end)
it('passes', function() end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, not not output:find('one_spec.lua @ 2: trace [setup]', 1, true), output)
end)
it('does not keep suite-end callbacks from load-error files', function()
local marker = t.tmpname(false)
local suite_dir = write_suite({
['one_spec.lua'] = string.format(
[[
local harness = require('test.harness')
harness.on_suite_end(function()
local file = assert(io.open(%q, 'ab'))
file:write('load\n')
file:close()
end)
error('boom during load')
]],
marker
),
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(nil, uv.fs_stat(marker))
eq(true, not not output:find('boom during load', 1, true), output)
end)
it('reports load failures at the failing line', function()
local suite_dir = write_suite({
['one_spec.lua'] = [[
local ok = true
local broken = )
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, output:match('one_spec%.lua @ 3: .-one_spec%.lua %[load%]') ~= nil, output)
end)
it('reports required-module syntax errors at the real module line', function()
local suite_dir = write_suite({
['broken.lua'] = [[
local ok = true
local broken = )
]],
['one_spec.lua'] = [[
require('broken')
describe('one', function()
it('works', function() end)
end)
]],
})
local code, output = run_harness(suite_dir)
eq(1, code)
eq(true, output:match('broken%.lua @ 3: .-one_spec%.lua %[load%]') ~= nil, output)
end)
end)