From 1d9b682a58e66fe051129314503bfc2bdc3fa2bd Mon Sep 17 00:00:00 2001 From: "M.Schirmer" Date: Sat, 3 Jan 2026 15:19:35 +0100 Subject: [PATCH] fix quickfix-list filling for TSamFailedOnly --- lua/test-samurai/core.lua | 18 ++++- tests/test_samurai_core_spec.lua | 116 +++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/lua/test-samurai/core.lua b/lua/test-samurai/core.lua index 2ca42dd..bb27b13 100644 --- a/lua/test-samurai/core.lua +++ b/lua/test-samurai/core.lua @@ -1830,6 +1830,7 @@ local function run_command(command, opts) end local items = {} local failures_for_qf = failures + local fallback_failures = options.qf_fallback_failures if options.track_scope and type(state.last_scope_failures) == "table" then local merged = {} local seen = {} @@ -1865,8 +1866,20 @@ local function run_command(command, opts) end failures_for_qf = merged end + if (not failures_for_qf or #failures_for_qf == 0) and type(fallback_failures) == "table" then + local merged = {} + local seen = {} + for _, name in ipairs(fallback_failures) do + if name and not seen[name] then + seen[name] = true + table.insert(merged, name) + end + end + failures_for_qf = merged + end if #failures_for_qf > 0 and runner and type(runner.collect_failed_locations) == "function" then - local ok_collect, collected = pcall(runner.collect_failed_locations, failures_for_qf, command, options.scope_kind) + local scope_kind = options.qf_scope_kind or options.scope_kind + local ok_collect, collected = pcall(runner.collect_failed_locations, failures_for_qf, command, scope_kind) if ok_collect and type(collected) == "table" then items = collected end @@ -2066,7 +2079,10 @@ function M.run_failed_only() end run_command(command, { save_last = false, + runner = runner, output_parser = parser or (runner and runner.parse_results), + qf_fallback_failures = state.last_scope_failures, + qf_scope_kind = state.last_scope_kind, }) end diff --git a/tests/test_samurai_core_spec.lua b/tests/test_samurai_core_spec.lua index a0fbe01..e924fe3 100644 --- a/tests/test_samurai_core_spec.lua +++ b/tests/test_samurai_core_spec.lua @@ -32,4 +32,120 @@ describe("test-samurai core (no bundled runners)", function() assert.equals(1, #notified) assert.equals("[test-samurai] no runner installed for this kind of test", notified[1].msg) end) + + it("fills quickfix after failed-only runs using last failures", function() + local runner = { + name = "test-runner", + } + + function runner.is_test_file(_bufnr) + return true + end + + function runner.find_nearest(bufnr, _row, _col) + return { file = vim.api.nvim_buf_get_name(bufnr), cwd = vim.loop.cwd(), test_name = "TestA" } + end + + function runner.build_command(spec) + return { cmd = { "echo", "nearest" }, cwd = spec.cwd } + end + + function runner.build_file_command(_bufnr) + return { cmd = { "echo", "file" } } + end + + function runner.build_all_command(_bufnr) + return { cmd = { "echo", "all" } } + end + + function runner.build_failed_command(last_command, _failures, _scope_kind) + return { cmd = { "echo", "failed" }, cwd = last_command and last_command.cwd or nil } + end + + function runner.parse_results(output) + local passes = {} + local failures = {} + if type(output) == "string" then + if output:find("FAIL TestA", 1, true) then + failures = { "TestA" } + end + if output:find("PASS TestA", 1, true) then + passes = { "TestA" } + end + end + return { passes = passes, failures = failures, skips = {} } + end + + function runner.output_parser() + return { + on_line = function(line, _state) + if line == "FAIL TestA" then + return { passes = {}, failures = { "TestA" }, skips = {} } + end + if line == "PASS TestA" then + return { passes = { "TestA" }, failures = {}, skips = {} } + end + return nil + end, + on_complete = function(output, _state) + return runner.parse_results(output) + end, + } + end + + function runner.parse_test_output(_output) + return {} + end + + function runner.collect_failed_locations(failures, _command, _scope_kind) + local items = {} + for _, name in ipairs(failures or {}) do + table.insert(items, { filename = "file", lnum = 1, col = 1, text = name }) + end + return items + end + + package.loaded["test-samurai-test-runner"] = runner + test_samurai.setup({ runner_modules = { "test-samurai-test-runner" } }) + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(bufnr, "/tmp/test_runner_quickfix.go") + vim.bo[bufnr].filetype = "go" + vim.api.nvim_set_current_buf(bufnr) + + local qf_calls = {} + local orig_setqflist = vim.fn.setqflist + vim.fn.setqflist = function(_, _, opts) + if opts and type(opts.items) == "table" then + table.insert(qf_calls, opts.items) + end + end + + local job_outputs = { + { "FAIL TestA" }, + { "PASS TestA" }, + } + local orig_jobstart = vim.fn.jobstart + vim.fn.jobstart = function(_cmd, opts) + local lines = table.remove(job_outputs, 1) or {} + if opts.on_stdout then + opts.on_stdout(nil, lines, nil) + end + if opts.on_exit then + opts.on_exit(nil, 0, nil) + end + return 1 + end + + core.run_nearest() + core.run_failed_only() + + vim.fn.jobstart = orig_jobstart + vim.fn.setqflist = orig_setqflist + + local last = qf_calls[#qf_calls] + assert.is_true(type(last) == "table") + assert.equals(1, #last) + assert.equals("TestA", last[1].text) + end) end)