add fine tuning and unit testing
Some checks failed
tests / test (push) Failing after 44s

This commit is contained in:
2025-12-31 21:25:35 +01:00
parent 3285cd2383
commit d0512b43c9
7 changed files with 762 additions and 94 deletions

View File

@@ -0,0 +1,28 @@
name: tests
on:
push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Neovim
run: |
sudo apt-get update
sudo apt-get install -y snapd
sudo snap install nvim --classic
- name: Install plenary.nvim
run: |
mkdir -p "$HOME/.local/share/nvim/site/pack/packer/start"
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim \
"$HOME/.local/share/nvim/site/pack/packer/start/plenary.nvim"
- name: Run tests
run: |
nvim --version
bash run_test.sh

View File

@@ -137,3 +137,7 @@ return runner
- parse_test_output implementiert
- collect_failed_locations implementiert
- command_spec `{ cmd, cwd }` korrekt zurückgegeben
## Repository-Hinweis
- `README.md` bei Änderungen am Runner immer mit aktualisieren.

View File

@@ -53,3 +53,10 @@ Main plugin: https://gitea.mschirmer.com/m13r/test-samurai.nvim
## Usage
Use the standard `test-samurai.nvim` commands (e.g. `TSamNearest`, `TSamFile`, `TSamAll`, `TSamFailedOnly`).
## Notes
- Subtests (`t.Run`) are reported as separate entries via `go test -json -v`.
- Subtest selection uses anchored patterns (`^Parent$/^Sub$`) to avoid matching the same subtest name in other tests.
- `go test` cannot scope execution to a single file; if two tests in the same package share the same test and subtest names, both will run.
- Result lists are ordered so that parent tests appear before their subtests within each status.

View File

@@ -3,65 +3,330 @@ local runner = {
framework = "go",
}
local function is_test_name(name)
return name:match("^Test%w+") or name:match("^Example%w+") or name:match("^Benchmark%w+")
local function get_buf_path(bufnr)
return vim.api.nvim_buf_get_name(bufnr)
end
local function escape_regex(text)
if vim and vim.pesc then
return vim.pesc(text)
end
return text:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
local function get_buf_lines(bufnr)
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
end
local function find_go_mod_root(start_dir)
if not start_dir or start_dir == "" then
start_dir = vim.fn.getcwd()
end
local start_path = vim.fn.fnamemodify(start_dir, ":p")
local mod_path = vim.fn.findfile("go.mod", start_path .. ";")
if mod_path == "" then
local function find_root(path, markers)
if not path or path == "" then
return nil
end
return vim.fn.fnamemodify(mod_path, ":p:h")
local dir = vim.fs.dirname(path)
if not dir or dir == "" then
return nil
end
local found = vim.fs.find(markers, { path = dir, upward = true })
if not found or not found[1] then
return nil
end
return vim.fs.dirname(found[1])
end
local function collect_results(output)
local passes = {}
local failures = {}
local skips = {}
local seen = { passes = {}, failures = {}, skips = {} }
local function find_block_end(lines, start_idx)
local depth = 0
local started = false
for i = start_idx, #lines do
local line = lines[i]
for j = 1, #line do
local ch = line:sub(j, j)
if ch == "{" then
depth = depth + 1
started = true
elseif ch == "}" then
if started then
depth = depth - 1
if depth == 0 then
return i - 1
end
end
end
end
end
return #lines - 1
end
for line in output:gmatch("[^\n]+") do
local name = line:match("^%-%-%- PASS:%s+(%S+)")
if name and not seen.passes[name] then
seen.passes[name] = true
passes[#passes + 1] = name
local function find_test_functions(lines)
local funcs = {}
for i, line in ipairs(lines) do
local name = line:match("^%s*func%s+([%w_]+)%s*%(")
if not name then
name = line:match("^%s*func%s+%([^)]-%)%s+([%w_]+)%s*%(")
end
if name and line:find("%*testing%.T") then
local start_0 = i - 1
local end_0 = find_block_end(lines, i)
table.insert(funcs, {
name = name,
start = start_0,
["end"] = end_0,
})
end
end
return funcs
end
local function find_t_runs(lines, func)
local subtests = {}
for i = func.start + 1, func["end"] do
local line = lines[i + 1]
if line then
local name = line:match("t%.Run%(%s*['\"]([^'\"]+)['\"]")
if name then
local start_idx = i + 1
local end_0 = find_block_end(lines, start_idx)
table.insert(subtests, {
name = name,
start = start_idx - 1,
["end"] = end_0,
})
end
end
end
return subtests
end
local function escape_go_regex(text)
text = text or ""
return (text:gsub("([\\.^$|()%%[%]{}*+?%-])", "\\\\%1"))
end
local function build_run_pattern(spec)
local name = spec.test_path or spec.test_name or ""
if spec.scope == "function" then
local escaped = escape_go_regex(name)
return "^" .. escaped .. "$"
end
name = line:match("^%-%-%- FAIL:%s+(%S+)")
if name and not seen.failures[name] then
seen.failures[name] = true
failures[#failures + 1] = name
local parts = vim.split(name, "/", { plain = true })
local anchored = {}
for _, part in ipairs(parts) do
anchored[#anchored + 1] = "^" .. escape_go_regex(part) .. "$"
end
return table.concat(anchored, "/")
end
local function build_pkg_arg(spec)
local file = spec.file
local cwd = spec.cwd
if not file or not cwd or file == "" or cwd == "" then
return "./..."
end
name = line:match("^%-%-%- SKIP:%s+(%S+)")
if name and not seen.skips[name] then
seen.skips[name] = true
skips[#skips + 1] = name
local dir = vim.fs.dirname(file)
if dir == cwd then
return "./"
end
if file:sub(1, #cwd) ~= cwd then
return "./..."
end
local rel = dir:sub(#cwd + 2)
if not rel or rel == "" then
return "./"
end
return "./" .. rel
end
local function collect_unique(list)
local out = {}
local seen = {}
for _, item in ipairs(list) do
if item and item ~= "" and not seen[item] then
seen[item] = true
table.insert(out, item)
end
end
return out
end
local function order_by_root(names)
local roots = {}
local seen_root = {}
local buckets = {}
for _, name in ipairs(names) do
local root = name:match("^[^/]+") or name
if not seen_root[root] then
seen_root[root] = true
table.insert(roots, root)
end
buckets[root] = buckets[root] or { main = nil, subs = {} }
if name == root then
buckets[root].main = name
else
table.insert(buckets[root].subs, name)
end
end
return passes, failures, skips
local ordered = {}
for _, root in ipairs(roots) do
local bucket = buckets[root]
if bucket.main then
table.insert(ordered, bucket.main)
end
for _, sub in ipairs(bucket.subs) do
table.insert(ordered, sub)
end
end
return ordered
end
local function order_with_display(names, display_map)
local ordered = order_by_root(names)
local display = {}
for _, name in ipairs(ordered) do
display[#display + 1] = display_map[name] or name
end
return ordered, display
end
local function display_name(name)
if not name or name == "" then
return name
end
if name:find("/", 1, true) then
return name
end
return name
end
local function split_output_lines(text)
if not text or text == "" then
return {}
end
local lines = vim.split(text, "\n", { plain = true })
if #lines > 0 and lines[#lines] == "" then
table.remove(lines, #lines)
end
return lines
end
local function normalize_go_name(name)
if not name or name == "" then
return nil
end
return (name:gsub("%s+", "_"))
end
local function add_location(target, key, file, line, label)
if not key or key == "" or not file or file == "" or not line then
return
end
local text = label or key
if not target[key] then
target[key] = {}
end
table.insert(target[key], {
filename = file,
lnum = line,
col = 1,
text = text,
})
end
local function collect_file_locations(file, target)
local ok, lines = pcall(vim.fn.readfile, file)
if not ok or type(lines) ~= "table" then
return
end
local funcs = find_test_functions(lines)
for _, fn in ipairs(funcs) do
add_location(target, fn.name, file, fn.start + 1, fn.name)
local normalized = normalize_go_name(fn.name)
if normalized and normalized ~= fn.name then
add_location(target, normalized, file, fn.start + 1, fn.name)
end
for _, sub in ipairs(find_t_runs(lines, fn)) do
local full = fn.name .. "/" .. sub.name
add_location(target, full, file, sub.start + 1, full)
local normalized_full = normalize_go_name(full)
if normalized_full and normalized_full ~= full then
add_location(target, normalized_full, file, sub.start + 1, full)
end
end
end
end
local function collect_go_test_files(root)
if not root or root == "" then
root = vim.loop.cwd()
end
local files = vim.fn.globpath(root, "**/*_test.go", false, true)
if type(files) ~= "table" then
return {}
end
return files
end
function runner.is_test_file(bufnr)
local name = vim.api.nvim_buf_get_name(bufnr)
return name:sub(-8) == "_test.go"
local path = get_buf_path(bufnr)
if not path or path == "" then
return false
end
return path:sub(-8) == "_test.go"
end
function runner.find_nearest(bufnr, row, _col)
local line_count = vim.api.nvim_buf_line_count(bufnr)
if not runner.is_test_file(bufnr) then
return nil, "not a Go test file"
end
local lines = get_buf_lines(bufnr)
local funcs = find_test_functions(lines)
local current
for _, f in ipairs(funcs) do
if row >= f.start and row <= f["end"] then
current = f
break
end
end
local path = get_buf_path(bufnr)
local root = find_root(path, { "go.mod", ".git" })
local cwd = root or vim.fs.dirname(path)
if current then
local subtests = find_t_runs(lines, current)
local inside_sub
for _, sub in ipairs(subtests) do
if row >= sub.start and row <= sub["end"] then
inside_sub = sub
break
end
end
if inside_sub then
local full = current.name .. "/" .. inside_sub.name
return {
file = path,
cwd = cwd,
test_path = full,
test_name = full,
scope = "subtest",
func = current.name,
subtest = inside_sub.name,
}
end
return {
file = path,
cwd = cwd,
test_path = current.name,
test_name = current.name,
scope = "function",
func = current.name,
}
end
local line_count = #lines
local idx = row or (line_count - 1)
if idx < 0 then
idx = 0
@@ -69,7 +334,6 @@ function runner.find_nearest(bufnr, row, _col)
idx = line_count - 1
end
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, line_count, false)
for i = idx + 1, 1, -1 do
local line = lines[i]
local name = line:match("^%s*func%s+(Test%w+)%s*%(")
@@ -82,14 +346,13 @@ function runner.find_nearest(bufnr, row, _col)
name = line:match("^%s*func%s+(Benchmark%w+)%s*%(")
kind = "benchmark"
end
if name and is_test_name(name) then
local file = vim.api.nvim_buf_get_name(bufnr)
local cwd = vim.fn.fnamemodify(file, ":p:h")
if name then
return {
file = file,
file = path,
cwd = cwd,
test_path = name,
test_name = name,
full_name = name,
scope = "function",
kind = kind,
}
end
@@ -99,31 +362,69 @@ function runner.find_nearest(bufnr, row, _col)
end
function runner.build_command(spec)
local name = spec.full_name or spec.test_name
if spec.kind == "benchmark" then
return {
cmd = { "go", "test", "-run", "^$", "-bench", "^" .. escape_regex(name) .. "$" },
cmd = { "go", "test", "-json", "-v", "-run", "^$", "-bench", "^" .. escape_go_regex(spec.test_path) .. "$" },
cwd = spec.cwd,
}
end
local pattern = build_run_pattern(spec)
local target = build_pkg_arg(spec)
return {
cmd = { "go", "test", "-run", "^" .. escape_regex(name) .. "$" },
cmd = { "go", "test", "-json", "-v", target, "-run", pattern },
cwd = spec.cwd,
}
end
function runner.build_file_command(bufnr)
local file = vim.api.nvim_buf_get_name(bufnr)
local cwd = vim.fn.fnamemodify(file, ":p:h")
return { cmd = { "go", "test" }, cwd = cwd }
local path = get_buf_path(bufnr)
if not path or path == "" then
return nil
end
local root = find_root(path, { "go.mod", ".git" })
if not root or root == "" then
root = vim.loop.cwd()
end
local spec = { file = path, cwd = root }
local pkg = build_pkg_arg(spec)
local cmd = { "go", "test", "-json", "-v", pkg }
local lines = get_buf_lines(bufnr)
local funcs = find_test_functions(lines)
local names = {}
for _, fn in ipairs(funcs) do
table.insert(names, fn.name)
end
names = collect_unique(names)
if #names > 0 then
local pattern_parts = {}
for _, name in ipairs(names) do
table.insert(pattern_parts, escape_go_regex(name))
end
local pattern = "^(" .. table.concat(pattern_parts, "|") .. ")$"
table.insert(cmd, "-run")
table.insert(cmd, pattern)
end
return {
cmd = cmd,
cwd = root,
}
end
function runner.build_all_command(bufnr)
local file = vim.api.nvim_buf_get_name(bufnr)
local cwd = vim.fn.fnamemodify(file, ":p:h")
local root = find_go_mod_root(cwd) or cwd
return { cmd = { "go", "test", "./..." }, cwd = root }
local path = get_buf_path(bufnr)
local root
if path and path ~= "" then
root = find_root(path, { "go.mod", ".git" })
end
if not root or root == "" then
root = vim.loop.cwd()
end
local cmd = { "go", "test", "-json", "-v", "./..." }
return {
cmd = cmd,
cwd = root,
}
end
function runner.build_failed_command(last_command, failures, _scope_kind)
@@ -131,67 +432,151 @@ function runner.build_failed_command(last_command, failures, _scope_kind)
if last_command and last_command.cmd then
return { cmd = last_command.cmd, cwd = last_command.cwd }
end
return { cmd = { "go", "test" } }
return { cmd = { "go", "test", "-json", "-v" } }
end
local escaped = {}
for _, name in ipairs(failures) do
escaped[#escaped + 1] = escape_regex(name)
local pattern_parts = {}
for _, name in ipairs(failures or {}) do
if name and name ~= "" then
local spec = { test_path = name, scope = name:find("/", 1, true) and "subtest" or "function" }
table.insert(pattern_parts, build_run_pattern(spec))
end
end
local pattern = "(" .. table.concat(pattern_parts, "|") .. ")"
local cmd = {}
local skip_next = false
for _, arg in ipairs(last_command and last_command.cmd or {}) do
if skip_next then
skip_next = false
elseif arg == "-run" then
skip_next = true
else
table.insert(cmd, arg)
end
end
if #cmd == 0 then
cmd = { "go", "test", "-json", "-v" }
end
table.insert(cmd, "-run")
table.insert(cmd, pattern)
local pattern = "^(" .. table.concat(escaped, "|") .. ")$"
return {
cmd = { "go", "test", "-run", pattern },
cmd = cmd,
cwd = last_command and last_command.cwd or nil,
}
end
function runner.parse_results(output)
local passes, failures, skips = collect_results(output)
return { passes = passes, failures = failures, skips = skips }
if not output or output == "" then
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
end
local passes = {}
local failures = {}
local skips = {}
local display = { passes = {}, failures = {}, skips = {} }
local pass_display = {}
local fail_display = {}
local skip_display = {}
local seen_pass = {}
local seen_fail = {}
local seen_skip = {}
for line in output:gmatch("[^\n]+") do
local ok, data = pcall(vim.json.decode, line)
if ok and type(data) == "table" then
if data.Test and data.Test ~= "" then
if data.Action == "pass" then
if not seen_pass[data.Test] then
seen_pass[data.Test] = true
table.insert(passes, data.Test)
pass_display[data.Test] = display_name(data.Test)
end
elseif data.Action == "fail" then
if not seen_fail[data.Test] then
seen_fail[data.Test] = true
table.insert(failures, data.Test)
fail_display[data.Test] = display_name(data.Test)
end
elseif data.Action == "skip" then
if not seen_skip[data.Test] then
seen_skip[data.Test] = true
table.insert(skips, data.Test)
skip_display[data.Test] = display_name(data.Test)
end
end
end
end
end
passes = collect_unique(passes)
failures = collect_unique(failures)
skips = collect_unique(skips)
passes, display.passes = order_with_display(passes, pass_display)
failures, display.failures = order_with_display(failures, fail_display)
skips, display.skips = order_with_display(skips, skip_display)
return { passes = passes, failures = failures, skips = skips, display = display }
end
function runner.parse_test_output(output)
local results = {}
local current = nil
local out = {}
if not output or output == "" then
return out
end
for line in output:gmatch("[^\n]+") do
local name = line:match("^=== RUN%s+(%S+)")
if name then
current = name
results[current] = results[current] or {}
elseif line:match("^%-%-%- %u+:%s+%S+") then
current = nil
elseif current then
results[current] = results[current] or {}
results[current][#results[current] + 1] = line
local ok, data = pcall(vim.json.decode, line)
if ok and type(data) == "table" and data.Action == "output" and data.Test and data.Output then
if not out[data.Test] then
out[data.Test] = {}
end
for _, item in ipairs(split_output_lines(data.Output)) do
table.insert(out[data.Test], item)
end
end
return results
end
return out
end
function runner.collect_failed_locations(failures, _command, _scope_kind)
function runner.collect_failed_locations(failures, command, scope_kind)
if type(failures) ~= "table" or #failures == 0 then
return {}
end
local files = {}
if scope_kind == "all" then
files = collect_go_test_files(command and command.cwd or nil)
elseif command and command.file then
files = { command.file }
end
if #files == 0 then
return {}
end
local locations = {}
for _, file in ipairs(files) do
collect_file_locations(file, locations)
end
local items = {}
if not failures then
return items
end
for _, failure in ipairs(failures) do
local filename, lnum, col = failure:match("([^:%s]+%.go):(%d+):(%d+)")
if not filename then
filename, lnum = failure:match("([^:%s]+%.go):(%d+)")
end
if filename and lnum then
items[#items + 1] = {
filename = filename,
lnum = tonumber(lnum),
col = tonumber(col) or 1,
text = failure,
}
local seen = {}
local function add_locations(name, locs)
for _, loc in ipairs(locs or {}) do
local key = string.format("%s:%d:%s", loc.filename or "", loc.lnum or 0, loc.text or name or "")
if not seen[key] then
seen[key] = true
table.insert(items, loc)
end
end
end
for _, name in ipairs(failures) do
local direct = locations[name]
if direct then
add_locations(name, direct)
elseif not name:find("/", 1, true) then
for full, locs in pairs(locations) do
if full:sub(-#name - 1) == "/" .. name then
add_locations(full, locs)
end
end
end
end
return items
end

3
run_test.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests" -c qa

15
tests/minimal_init.lua Normal file
View File

@@ -0,0 +1,15 @@
vim.cmd("set rtp^=.")
local data_path = vim.fn.stdpath("data")
local plenary_paths = {
data_path .. "/site/pack/packer/start/plenary.nvim",
data_path .. "/lazy/plenary.nvim",
}
for _, path in ipairs(plenary_paths) do
if vim.fn.isdirectory(path) == 1 then
vim.cmd("set rtp^=" .. path)
end
end
vim.cmd("runtime! plugin/plenary.vim")

View File

@@ -0,0 +1,226 @@
local runner = require("test-samurai-go-runner")
describe("test-samurai-go-runner", function()
it("detects Go test files by suffix", function()
local bufnr1 = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr1, "/tmp/go_suffix_test.go")
assert.is_true(runner.is_test_file(bufnr1))
local bufnr2 = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr2, "/tmp/go_main.go")
assert.is_false(runner.is_test_file(bufnr2))
end)
it("finds subtest when cursor is inside t.Run block", function()
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/go_subtest_test.go")
local lines = {
"package main",
"import \"testing\"",
"",
"func TestFoo(t *testing.T) {",
" t.Run(\"first\", func(t *testing.T) {",
" -- inside first",
" })",
"",
" t.Run(\"second\", func(t *testing.T) {",
" -- inside second",
" })",
"}",
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/go.mod" }
end
local row_inside_first = 5
local spec, err = runner.find_nearest(bufnr, row_inside_first, 0)
vim.fs.find = orig_fs_find
assert.is_nil(err)
assert.is_not_nil(spec)
assert.equals("TestFoo/first", spec.test_path)
assert.equals("subtest", spec.scope)
assert.is_true(spec.file:match("go_subtest_test%.go$") ~= nil)
assert.is_true(spec.cwd:match("/tmp$") ~= nil)
end)
it("falls back to whole test function when between subtests", function()
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/go_between_test.go")
local lines = {
"package main",
"import \"testing\"",
"",
"func TestFoo(t *testing.T) {",
" t.Run(\"first\", func(t *testing.T) {",
" -- inside first",
" })",
"",
" t.Run(\"second\", func(t *testing.T) {",
" -- inside second",
" })",
"}",
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/go.mod" }
end
local row_between = 7
local spec, err = runner.find_nearest(bufnr, row_between, 0)
vim.fs.find = orig_fs_find
assert.is_nil(err)
assert.is_not_nil(spec)
assert.equals("TestFoo", spec.test_path)
assert.equals("function", spec.scope)
assert.is_true(spec.file:match("go_between_test%.go$") ~= nil)
assert.is_true(spec.cwd:match("/tmp$") ~= nil)
end)
it("build_command uses current package and correct run pattern", function()
local spec_sub = {
file = "/tmp/project/pkg/foo_test.go",
cwd = "/tmp/project",
test_path = "TestFoo/first",
scope = "subtest",
}
local cmd_spec_sub = runner.build_command(spec_sub)
assert.are.same(
{ "go", "test", "-json", "-v", "./pkg", "-run", "^TestFoo$/^first$" },
cmd_spec_sub.cmd
)
local spec_func = {
file = "/tmp/project/foo_test.go",
cwd = "/tmp/project",
test_path = "TestFoo",
scope = "function",
}
local cmd_spec_func = runner.build_command(spec_func)
assert.are.same(
{ "go", "test", "-json", "-v", "./", "-run", "^TestFoo$" },
cmd_spec_func.cmd
)
end)
it("build_file_command uses exact test names from current file", function()
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/get_test.go")
local lines = {
"package main",
"import \"testing\"",
"",
"func TestHandleGet(t *testing.T) {",
" t.Run(\"returns_200\", func(t *testing.T) {",
" -- inside test",
" })",
"}",
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/project/go.mod" }
end
local cmd_spec = runner.build_file_command(bufnr)
vim.fs.find = orig_fs_find
assert.are.same(
{ "go", "test", "-json", "-v", "./", "-run", "^(TestHandleGet)$" },
cmd_spec.cmd
)
assert.equals("/tmp/project", cmd_spec.cwd)
end)
it("parse_results reports subtests and display names", function()
local output = table.concat({
vim.json.encode({ Action = "run", Test = "TestFoo" }),
vim.json.encode({ Action = "pass", Test = "TestFoo/first" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/second" }),
vim.json.encode({ Action = "skip", Test = "TestFoo/third" }),
}, "\n")
local results = runner.parse_results(output)
assert.are.same({ "TestFoo/first" }, results.passes)
assert.are.same({ "TestFoo/second" }, results.failures)
assert.are.same({ "TestFoo/third" }, results.skips)
assert.are.same({ "TestFoo/first" }, results.display.passes)
assert.are.same({ "TestFoo/second" }, results.display.failures)
assert.are.same({ "TestFoo/third" }, results.display.skips)
end)
it("orders parent tests before subtests within each status", function()
local output = table.concat({
vim.json.encode({ Action = "fail", Test = "TestHandleGet/returns_200" }),
vim.json.encode({ Action = "fail", Test = "TestHandleGet" }),
vim.json.encode({ Action = "pass", Test = "TestOther/alpha" }),
vim.json.encode({ Action = "pass", Test = "TestOther" }),
}, "\n")
local results = runner.parse_results(output)
assert.are.same({ "TestHandleGet", "TestHandleGet/returns_200" }, results.failures)
assert.are.same({ "TestOther", "TestOther/alpha" }, results.passes)
assert.are.same({ "TestHandleGet", "TestHandleGet/returns_200" }, results.display.failures)
assert.are.same({ "TestOther", "TestOther/alpha" }, results.display.passes)
end)
it("parse_test_output groups output per test", function()
local output = table.concat({
vim.json.encode({ Action = "output", Test = "TestFoo", Output = "line1\n" }),
vim.json.encode({ Action = "output", Test = "TestFoo", Output = "line2\n" }),
vim.json.encode({ Action = "output", Test = "TestFoo/first", Output = "sub1\n" }),
}, "\n")
local results = runner.parse_test_output(output)
assert.are.same({ "line1", "line2" }, results["TestFoo"])
assert.are.same({ "sub1" }, results["TestFoo/first"])
end)
it("build_failed_command narrows to failed tests", function()
local last_command = {
cmd = { "go", "test", "-json", "-v", "./", "-run", "^TestFoo($|/)" },
cwd = "/tmp/project",
}
local failures = { "TestFoo/first", "TestBar" }
local cmd_spec = runner.build_failed_command(last_command, failures, "file")
assert.are.same(
{ "go", "test", "-json", "-v", "./", "-run", "(^TestFoo$/^first$|^TestBar$)" },
cmd_spec.cmd
)
end)
it("collect_failed_locations finds subtest positions", function()
local temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, "p")
local file = temp_dir .. "/sample_test.go"
local lines = {
"package main",
"import \"testing\"",
"",
"func TestFoo(t *testing.T) {",
" t.Run(\"first\", func(t *testing.T) {",
" -- inside test",
" })",
"}",
}
vim.fn.writefile(lines, file)
local items = runner.collect_failed_locations({ "TestFoo/first" }, { cwd = temp_dir }, "all")
assert.is_true(#items > 0)
assert.equals(file, items[1].filename)
assert.equals(5, items[1].lnum)
end)
end)