create complete runner by AGENTS.md and ChatGPT-Codex
This commit is contained in:
139
AGENTS.md
Normal file
139
AGENTS.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# runner-agents.md — test-samurai Runner-API
|
||||||
|
|
||||||
|
Ziel: Diese Datei beschreibt die öffentliche Runner-API, die ein neuer Runner
|
||||||
|
implementieren muss, damit alle Commands vollständig unterstützt werden.
|
||||||
|
|
||||||
|
## Modulform (Pflicht)
|
||||||
|
|
||||||
|
- Der Runner ist ein Lua-Modul, das eine Table mit Funktionen zurückgibt.
|
||||||
|
- Beispiel:
|
||||||
|
- `local runner = {}`
|
||||||
|
- `return runner`
|
||||||
|
|
||||||
|
## Pflichtfunktionen (volle Command-Unterstützung)
|
||||||
|
|
||||||
|
- `is_test_file(bufnr) -> boolean`
|
||||||
|
- Wird für die Runner-Auswahl genutzt.
|
||||||
|
- `find_nearest(bufnr, row, col) -> spec|nil, err?`
|
||||||
|
- Für `TSamNearest`.
|
||||||
|
- `spec.file` muss gesetzt sein.
|
||||||
|
- Bei Fehler/kein Treffer: `nil, "reason"` zurückgeben.
|
||||||
|
- `build_command(spec) -> command_spec`
|
||||||
|
- Für `TSamNearest`.
|
||||||
|
- `build_file_command(bufnr) -> command_spec`
|
||||||
|
- Für `TSamFile`.
|
||||||
|
- `build_all_command(bufnr) -> command_spec`
|
||||||
|
- Für `TSamAll`.
|
||||||
|
- `build_failed_command(last_command, failures, scope_kind) -> command_spec`
|
||||||
|
- Für `TSamFailedOnly`.
|
||||||
|
|
||||||
|
## Output-Parsing (Listing + Summary)
|
||||||
|
|
||||||
|
Genau eine der folgenden Varianten muss vorhanden sein:
|
||||||
|
|
||||||
|
- `parse_results(output) -> results`
|
||||||
|
- `results` muss enthalten:
|
||||||
|
- `passes` (Array von Namen)
|
||||||
|
- `failures` (Array von Namen)
|
||||||
|
- `skips` (Array von Namen)
|
||||||
|
- Optional:
|
||||||
|
- `display = { passes = {}, failures = {}, skips = {} }`
|
||||||
|
- `failures_all` (für Streaming-Parser, um alle bisherigen Failures zu liefern)
|
||||||
|
- Wenn `display` fehlt, werden `passes/failures/skips` direkt im Listing angezeigt.
|
||||||
|
- 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).
|
||||||
|
|
||||||
|
## Detail-Output (TSamShowOutput / <cr> im Listing)
|
||||||
|
|
||||||
|
- `parse_test_output(output) -> table`
|
||||||
|
- Rückgabeform:
|
||||||
|
- `{ [test_name] = { "line1", "line2", ... } }`
|
||||||
|
- `test_name` muss mit `results.*` korrespondieren (gleiches Namensschema).
|
||||||
|
|
||||||
|
## Quickfix-Unterstützung (Failures)
|
||||||
|
|
||||||
|
- `collect_failed_locations(failures, command, scope_kind) -> items`
|
||||||
|
- `items`: Array von Quickfix-Items
|
||||||
|
- `{ filename = "...", lnum = <number>, col = <number>, text = "..." }`
|
||||||
|
|
||||||
|
## Erwartete Datenformen
|
||||||
|
|
||||||
|
- `command_spec`:
|
||||||
|
- `{ cmd = { "binary", "arg1", ... }, cwd = "..." }`
|
||||||
|
- `cmd` darf nicht leer sein.
|
||||||
|
- `cwd` ist optional; wenn nicht gesetzt, nutzt der Core das aktuelle CWD.
|
||||||
|
- `spec` (von `find_nearest`):
|
||||||
|
- Muss mindestens `file` enthalten, z. B.:
|
||||||
|
- `{ file = "...", cwd = "...", test_name = "...", full_name = "...", kind = "..." }`
|
||||||
|
|
||||||
|
## Optional empfohlene Metadaten
|
||||||
|
|
||||||
|
- `name` (String)
|
||||||
|
- Wird in Fehlermeldungen und Logs angezeigt.
|
||||||
|
- `framework` (String)
|
||||||
|
- Wird zur Framework-Auswahl (z. B. JS) genutzt.
|
||||||
|
|
||||||
|
## Prompt-Beispiel
|
||||||
|
|
||||||
|
"Erstelle mir anhand der `runner-agents.md` einen neuen Runner für Rust."
|
||||||
|
|
||||||
|
## Minimaler Runner-Skeleton (Template)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local runner = {
|
||||||
|
name = "my-runner",
|
||||||
|
framework = "my-framework",
|
||||||
|
}
|
||||||
|
|
||||||
|
function runner.is_test_file(bufnr)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.find_nearest(bufnr, row, col)
|
||||||
|
return nil, "no test call found"
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_command(spec)
|
||||||
|
return { cmd = { "echo", "not-implemented" }, cwd = spec.cwd }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_file_command(bufnr)
|
||||||
|
return { cmd = { "echo", "not-implemented" } }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_all_command(bufnr)
|
||||||
|
return { cmd = { "echo", "not-implemented" } }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_failed_command(last_command, failures, scope_kind)
|
||||||
|
return { cmd = { "echo", "not-implemented" }, cwd = last_command and last_command.cwd or nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.parse_results(output)
|
||||||
|
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.parse_test_output(output)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.collect_failed_locations(failures, command, scope_kind)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return runner
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checkliste für neue Runner
|
||||||
|
|
||||||
|
- is_test_file implementiert
|
||||||
|
- find_nearest implementiert (setzt `spec.file`)
|
||||||
|
- build_command implementiert
|
||||||
|
- build_file_command implementiert
|
||||||
|
- build_all_command implementiert
|
||||||
|
- build_failed_command implementiert
|
||||||
|
- parse_results oder output_parser implementiert
|
||||||
|
- parse_test_output implementiert
|
||||||
|
- collect_failed_locations implementiert
|
||||||
|
- command_spec `{ cmd, cwd }` korrekt zurückgegeben
|
||||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# test-samurai-go-runner.nvim
|
||||||
|
|
||||||
|
Go runner for `test-samurai.nvim`.
|
||||||
|
|
||||||
|
Main plugin: https://gitea.mschirmer.com/m13r/test-samurai.nvim
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Detects Go test files (`*_test.go`).
|
||||||
|
- Finds nearest `Test*`, `Example*`, and `Benchmark*` functions.
|
||||||
|
- Builds `go test` commands for nearest, file, all, and failed-only runs.
|
||||||
|
- Parses `go test` output to list passes, failures, and skips.
|
||||||
|
|
||||||
|
## Installation (lazy.nvim)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
"m13r/test-samurai.nvim",
|
||||||
|
dependencies = {
|
||||||
|
"m13r/test-samurai-go-runner.nvim",
|
||||||
|
},
|
||||||
|
config = function()
|
||||||
|
require("test-samurai").setup({
|
||||||
|
runners = {
|
||||||
|
require("test-samurai-go-runner"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Development (lazy.nvim)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
"m13r/test-samurai.nvim",
|
||||||
|
dependencies = {
|
||||||
|
{
|
||||||
|
"test-samurai-go-runner.nvim",
|
||||||
|
dir = "/absolute/path/to/test-samurai-go-runner.nvim",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config = function()
|
||||||
|
require("test-samurai").setup({
|
||||||
|
runners = {
|
||||||
|
require("test-samurai-go-runner"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Use the standard `test-samurai.nvim` commands (e.g. `TSamNearest`, `TSamFile`, `TSamAll`, `TSamFailedOnly`).
|
||||||
198
lua/test-samurai-go-runner/init.lua
Normal file
198
lua/test-samurai-go-runner/init.lua
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
local runner = {
|
||||||
|
name = "go",
|
||||||
|
framework = "go",
|
||||||
|
}
|
||||||
|
|
||||||
|
local function is_test_name(name)
|
||||||
|
return name:match("^Test%w+") or name:match("^Example%w+") or name:match("^Benchmark%w+")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function escape_regex(text)
|
||||||
|
if vim and vim.pesc then
|
||||||
|
return vim.pesc(text)
|
||||||
|
end
|
||||||
|
return text:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
||||||
|
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
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return vim.fn.fnamemodify(mod_path, ":p:h")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function collect_results(output)
|
||||||
|
local passes = {}
|
||||||
|
local failures = {}
|
||||||
|
local skips = {}
|
||||||
|
local seen = { passes = {}, failures = {}, skips = {} }
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
name = line:match("^%-%-%- FAIL:%s+(%S+)")
|
||||||
|
if name and not seen.failures[name] then
|
||||||
|
seen.failures[name] = true
|
||||||
|
failures[#failures + 1] = name
|
||||||
|
end
|
||||||
|
|
||||||
|
name = line:match("^%-%-%- SKIP:%s+(%S+)")
|
||||||
|
if name and not seen.skips[name] then
|
||||||
|
seen.skips[name] = true
|
||||||
|
skips[#skips + 1] = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return passes, failures, skips
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.is_test_file(bufnr)
|
||||||
|
local name = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
return name:sub(-8) == "_test.go"
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.find_nearest(bufnr, row, _col)
|
||||||
|
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
||||||
|
local idx = row or (line_count - 1)
|
||||||
|
if idx < 0 then
|
||||||
|
idx = 0
|
||||||
|
elseif idx >= line_count then
|
||||||
|
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*%(")
|
||||||
|
local kind = "test"
|
||||||
|
if not name then
|
||||||
|
name = line:match("^%s*func%s+(Example%w+)%s*%(")
|
||||||
|
kind = "example"
|
||||||
|
end
|
||||||
|
if not name then
|
||||||
|
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")
|
||||||
|
return {
|
||||||
|
file = file,
|
||||||
|
cwd = cwd,
|
||||||
|
test_name = name,
|
||||||
|
full_name = name,
|
||||||
|
kind = kind,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, "no test function found"
|
||||||
|
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) .. "$" },
|
||||||
|
cwd = spec.cwd,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
cmd = { "go", "test", "-run", "^" .. escape_regex(name) .. "$" },
|
||||||
|
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 }
|
||||||
|
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 }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_failed_command(last_command, failures, _scope_kind)
|
||||||
|
if not failures or #failures == 0 then
|
||||||
|
if last_command and last_command.cmd then
|
||||||
|
return { cmd = last_command.cmd, cwd = last_command.cwd }
|
||||||
|
end
|
||||||
|
return { cmd = { "go", "test" } }
|
||||||
|
end
|
||||||
|
|
||||||
|
local escaped = {}
|
||||||
|
for _, name in ipairs(failures) do
|
||||||
|
escaped[#escaped + 1] = escape_regex(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local pattern = "^(" .. table.concat(escaped, "|") .. ")$"
|
||||||
|
return {
|
||||||
|
cmd = { "go", "test", "-run", pattern },
|
||||||
|
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 }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.parse_test_output(output)
|
||||||
|
local results = {}
|
||||||
|
local current = nil
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.collect_failed_locations(failures, _command, _scope_kind)
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
return runner
|
||||||
Reference in New Issue
Block a user