Compare commits
2 Commits
6505a91cce
...
e315a8e8f2
| Author | SHA1 | Date | |
|---|---|---|---|
|
e315a8e8f2
|
|||
|
1d9b682a58
|
@@ -79,6 +79,7 @@ Additional keymaps:
|
||||
## Runner architecture
|
||||
|
||||
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:
|
||||
|
||||
- `is_test_file`
|
||||
@@ -87,6 +88,9 @@ Required functions:
|
||||
- `build_file_command`
|
||||
- `build_all_command`
|
||||
- `build_failed_command`
|
||||
- `parse_results`
|
||||
- `output_parser` (must stream listing output via `on_line`)
|
||||
- `parse_test_output`
|
||||
- `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.
|
||||
@@ -100,6 +104,8 @@ No runner for your environment exists? No problem: use `runners-agents.md` to gu
|
||||
|
||||
## 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.
|
||||
|
||||
Run tests:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")`.
|
||||
- `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`
|
||||
- Wird für die Runner-Auswahl genutzt.
|
||||
@@ -29,10 +29,18 @@ implementieren muss, damit alle Commands vollständig unterstützt werden.
|
||||
- Für `TSamAll`.
|
||||
- `build_failed_command(last_command, failures, scope_kind) -> command_spec`
|
||||
- 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)
|
||||
|
||||
Genau eine der folgenden Varianten muss vorhanden sein:
|
||||
Beide Varianten müssen vorhanden sein:
|
||||
|
||||
- `parse_results(output) -> results`
|
||||
- `results` muss enthalten:
|
||||
@@ -46,6 +54,9 @@ Genau eine der folgenden Varianten muss vorhanden sein:
|
||||
- oder `output_parser() -> { on_line, on_complete }`
|
||||
- `on_line(line, 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
|
||||
|
||||
@@ -78,6 +89,25 @@ Genau eine der folgenden Varianten muss vorhanden sein:
|
||||
- `spec` (von `find_nearest`):
|
||||
- Muss mindestens `file` enthalten, z. B.:
|
||||
- `{ 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
|
||||
|
||||
@@ -126,6 +156,17 @@ function runner.parse_results(output)
|
||||
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
|
||||
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)
|
||||
return {}
|
||||
end
|
||||
@@ -145,21 +186,33 @@ return runner
|
||||
- build_file_command implementiert
|
||||
- build_all_command implementiert
|
||||
- build_failed_command implementiert
|
||||
- parse_results oder output_parser implementiert
|
||||
- parse_results implementiert
|
||||
- output_parser implementiert (Streaming)
|
||||
- parse_test_output implementiert
|
||||
- collect_failed_locations implementiert
|
||||
- command_spec `{ cmd, cwd }` korrekt zurückgegeben
|
||||
|
||||
## Projekt- und Prozessanforderungen
|
||||
|
||||
- Eine englischsprachige `README.md` ist zu erstellen.
|
||||
- Sie muss bei jedem weiteren Prompt aktualisiert werden, wenn die Aenderungen es erfordern.
|
||||
- Rolle: **TDD-first Entwickler**.
|
||||
- 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:
|
||||
- Neue Funktionen/Commands/Verhaltensaenderungen muessen getestet werden.
|
||||
- Tests nach jeder Code-Aenderung ausfuehren.
|
||||
- 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.
|
||||
- Neovim wird per AppImage installiert (kein `apt`).
|
||||
- Runner laeuft auf `gitea-act-runner` mit Raspberry Pi 5 (ARM).
|
||||
- Beispiel (anpassbarer Workflow):
|
||||
|
||||
```yaml
|
||||
@@ -176,6 +229,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
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
|
||||
run: bash run_test.sh
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user