Compare commits

..

2 Commits

Author SHA1 Message Date
e315a8e8f2 update ai prompt for external runner creation
All checks were successful
tests / test (push) Successful in 8s
2026-01-03 15:20:01 +01:00
1d9b682a58 fix quickfix-list filling for TSamFailedOnly 2026-01-03 15:19:35 +01:00
4 changed files with 203 additions and 7 deletions

View File

@@ -79,6 +79,7 @@ Additional keymaps:
## Runner architecture ## Runner architecture
Runners are standalone Lua modules. All runner modules are expected to implement the full interface so every command and keymap works. Runners are standalone Lua modules. All runner modules are expected to implement the full interface so every command and keymap works.
All functions are required (including the previously optional ones) and listing output must be streamed.
Required functions: Required functions:
- `is_test_file` - `is_test_file`
@@ -87,6 +88,9 @@ Required functions:
- `build_file_command` - `build_file_command`
- `build_all_command` - `build_all_command`
- `build_failed_command` - `build_failed_command`
- `parse_results`
- `output_parser` (must stream listing output via `on_line`)
- `parse_test_output`
- `collect_failed_locations` - `collect_failed_locations`
No runner for your environment exists? No problem: use `runners-agents.md` to guide an AI-assisted runner implementation tailored to your stack. No runner for your environment exists? No problem: use `runners-agents.md` to guide an AI-assisted runner implementation tailored to your stack.
@@ -100,6 +104,8 @@ No runner for your environment exists? No problem: use `runners-agents.md` to gu
## Development ## Development
Runner development guidelines, including required data formats for keymaps, tests (`run_test.sh`), and Gitea CI (Neovim AppImage on ARM runners), are documented in `runner-agents.md`.
Tests are written with `plenary.nvim` / `busted`. Mocks and stubs are allowed. Tests are written with `plenary.nvim` / `busted`. Mocks and stubs are allowed.
Run tests: Run tests:

View File

@@ -1830,6 +1830,7 @@ local function run_command(command, opts)
end end
local items = {} local items = {}
local failures_for_qf = failures local failures_for_qf = failures
local fallback_failures = options.qf_fallback_failures
if options.track_scope and type(state.last_scope_failures) == "table" then if options.track_scope and type(state.last_scope_failures) == "table" then
local merged = {} local merged = {}
local seen = {} local seen = {}
@@ -1865,8 +1866,20 @@ local function run_command(command, opts)
end end
failures_for_qf = merged failures_for_qf = merged
end 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 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 if ok_collect and type(collected) == "table" then
items = collected items = collected
end end
@@ -2066,7 +2079,10 @@ function M.run_failed_only()
end end
run_command(command, { run_command(command, {
save_last = false, save_last = false,
runner = runner,
output_parser = parser or (runner and runner.parse_results), output_parser = parser or (runner and runner.parse_results),
qf_fallback_failures = state.last_scope_failures,
qf_scope_kind = state.last_scope_kind,
}) })
end end

View File

@@ -13,7 +13,7 @@ implementieren muss, damit alle Commands vollständig unterstützt werden.
- Beispiel: `lua/test-samurai-go-runner/init.lua` -> `require("test-samurai-go-runner")`. - Beispiel: `lua/test-samurai-go-runner/init.lua` -> `require("test-samurai-go-runner")`.
- `lua/init.lua` wäre `require("init")` und ist kollisionsanfällig; vermeiden. - `lua/init.lua` wäre `require("init")` und ist kollisionsanfällig; vermeiden.
## Pflichtfunktionen (volle Command-Unterstützung) ## Pflichtfunktionen (volle Command- und Keymap-Unterstützung)
- `is_test_file(bufnr) -> boolean` - `is_test_file(bufnr) -> boolean`
- Wird für die Runner-Auswahl genutzt. - Wird für die Runner-Auswahl genutzt.
@@ -29,10 +29,18 @@ implementieren muss, damit alle Commands vollständig unterstützt werden.
- Für `TSamAll`. - Für `TSamAll`.
- `build_failed_command(last_command, failures, scope_kind) -> command_spec` - `build_failed_command(last_command, failures, scope_kind) -> command_spec`
- Für `TSamFailedOnly`. - Für `TSamFailedOnly`.
- `parse_results(output) -> results`
- Fallback-Parser für vollständigen Output.
- `output_parser() -> { on_line, on_complete }`
- Muss Streaming unterstützen (Listing wird live befüllt).
- `parse_test_output(output) -> table`
- Detail-Ausgabe pro Test (Detail-Float und `<cr>` im Listing).
- `collect_failed_locations(failures, command, scope_kind) -> items`
- Quickfix-Unterstützung (Keymap `<leader>qn`).
## Output-Parsing (Listing + Summary) ## Output-Parsing (Listing + Summary)
Genau eine der folgenden Varianten muss vorhanden sein: Beide Varianten müssen vorhanden sein:
- `parse_results(output) -> results` - `parse_results(output) -> results`
- `results` muss enthalten: - `results` muss enthalten:
@@ -46,6 +54,9 @@ Genau eine der folgenden Varianten muss vorhanden sein:
- oder `output_parser() -> { on_line, on_complete }` - oder `output_parser() -> { on_line, on_complete }`
- `on_line(line, state)` kann `results` liefern (siehe oben). - `on_line(line, state)` kann `results` liefern (siehe oben).
- `on_complete(output, state)` kann `results` liefern (siehe oben). - `on_complete(output, state)` kann `results` liefern (siehe oben).
- `on_line` muss Ergebnisse liefern, damit das Listing-Float immer gestreamt wird.
Hinweis: `parse_results` darf intern `output_parser().on_complete` nutzen, aber beide Funktionen müssen existieren.
## Listing-Gruppierung fuer Parent/Subtests ## Listing-Gruppierung fuer Parent/Subtests
@@ -78,6 +89,25 @@ Genau eine der folgenden Varianten muss vorhanden sein:
- `spec` (von `find_nearest`): - `spec` (von `find_nearest`):
- Muss mindestens `file` enthalten, z. B.: - Muss mindestens `file` enthalten, z. B.:
- `{ file = "...", cwd = "...", test_name = "...", full_name = "...", kind = "..." }` - `{ file = "...", cwd = "...", test_name = "...", full_name = "...", kind = "..." }`
- `results` (für Listing-Float + Keymaps):
- `{ passes = { "Name1", ... }, failures = { "Name2", ... }, skips = { "Name3", ... } }`
- Optional: `display = { passes = { "DisplayName1", ... }, failures = { "DisplayName2", ... }, skips = { "DisplayName3", ... } }`
- `failures` steuert `[ FAIL ]`-Zeilen im Listing und wird von `<leader>nf`/`<leader>pf` genutzt.
- `items` (für Quickfix):
- `{ { filename = "...", lnum = 1, col = 1, text = "..." }, ... }`
- Wird von `<leader>qn` verwendet.
## Keymaps (Datenlieferung)
- `<leader>nf` / `<leader>pf`
- benötigt `[ FAIL ]`-Einträge im Listing.
- Runner muss `results.failures` (und optional `display.failures`) liefern.
- `<leader>qn`
- springt in die Quickfix-Liste.
- Runner muss `collect_failed_locations` implementieren und gültige `items` liefern.
- `<cr>` im Listing
- öffnet Detail-Float.
- Runner muss `parse_test_output` liefern und Testnamen konsistent zu `results.*` halten.
## Optional empfohlene Metadaten ## Optional empfohlene Metadaten
@@ -126,6 +156,17 @@ function runner.parse_results(output)
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } } return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
end end
function runner.output_parser()
return {
on_line = function(_line, _state)
return nil
end,
on_complete = function(_output, _state)
return runner.parse_results(_output)
end,
}
end
function runner.parse_test_output(output) function runner.parse_test_output(output)
return {} return {}
end end
@@ -145,21 +186,33 @@ return runner
- build_file_command implementiert - build_file_command implementiert
- build_all_command implementiert - build_all_command implementiert
- build_failed_command implementiert - build_failed_command implementiert
- parse_results oder output_parser implementiert - parse_results implementiert
- output_parser implementiert (Streaming)
- parse_test_output implementiert - parse_test_output implementiert
- collect_failed_locations implementiert - collect_failed_locations implementiert
- command_spec `{ cmd, cwd }` korrekt zurückgegeben - command_spec `{ cmd, cwd }` korrekt zurückgegeben
## Projekt- und Prozessanforderungen ## Projekt- und Prozessanforderungen
- Eine englischsprachige `README.md` ist zu erstellen. - Rolle: **TDD-first Entwickler**.
- Sie muss bei jedem weiteren Prompt aktualisiert werden, wenn die Aenderungen es erfordern. - Jede neue Funktion, jedes neue Kommando und jede Verhaltensänderung muss durch Tests abgesichert sein.
- Nach jeder Code-Änderung Tests via `bash run_test.sh` ausführen und bei Fehlern korrigieren, bis alles grün ist.
- **Nicht raten**:
- Bei unklaren oder mehrdeutigen Anforderungen Arbeit stoppen und Klarstellung verlangen.
- TODO/NOTE im Code ist zulässig, stilles Raten nicht.
- **Keine stillen Änderungen**:
- Bestehende Features dürfen nicht unbemerkt geändert oder ersetzt werden.
- Notwendige Anpassungen zur Koexistenz mehrerer Features müssen klar erkennbar sein.
- Antworten immer auf Deutsch.
- Eine englischsprachige `README.md` ist zu erstellen und wird bei Änderungen automatisch aktualisiert.
- TDD-Vorgaben (aus `AGENTS.md`) uebernehmen: - TDD-Vorgaben (aus `AGENTS.md`) uebernehmen:
- Neue Funktionen/Commands/Verhaltensaenderungen muessen getestet werden. - Neue Funktionen/Commands/Verhaltensaenderungen muessen getestet werden.
- Tests nach jeder Code-Aenderung ausfuehren. - Tests nach jeder Code-Aenderung ausfuehren.
- Im Runner erstellter Quellcode ist ebenfalls zu testen. - Im Runner erstellter Quellcode ist ebenfalls zu testen.
- Eine eigene `run_test.sh` darf im Runner-Repo angelegt werden. - Eine eigene `run_test.sh` wird im Runner-Repo angelegt.
- Eine Gitea-Action ist zu erstellen, die bei jedem Push die Tests ausfuehrt. - Eine Gitea-Action ist zu erstellen, die bei jedem Push die Tests ausfuehrt.
- Neovim wird per AppImage installiert (kein `apt`).
- Runner laeuft auf `gitea-act-runner` mit Raspberry Pi 5 (ARM).
- Beispiel (anpassbarer Workflow): - Beispiel (anpassbarer Workflow):
```yaml ```yaml
@@ -176,6 +229,11 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Neovim (AppImage)
run: |
curl -L -o nvim.appimage https://github.com/neovim/neovim/releases/download/v0.11.4/nvim.appimage
chmod +x nvim.appimage
sudo mv nvim.appimage /usr/local/bin/nvim
- name: Run tests - name: Run tests
run: bash run_test.sh run: bash run_test.sh
``` ```

View File

@@ -32,4 +32,120 @@ describe("test-samurai core (no bundled runners)", function()
assert.equals(1, #notified) assert.equals(1, #notified)
assert.equals("[test-samurai] no runner installed for this kind of test", notified[1].msg) assert.equals("[test-samurai] no runner installed for this kind of test", notified[1].msg)
end) 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) end)