mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-10 05:01:28 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d9a68b666 | ||
|
|
ad152e0049 | ||
|
|
b4b0418daf |
No files matched your search
@@ -8,6 +8,12 @@ import (
|
|||||||
|
|
||||||
const commandReducerMinPassingLines = 12
|
const commandReducerMinPassingLines = 12
|
||||||
|
|
||||||
|
type commandOutputReducer struct {
|
||||||
|
kind string
|
||||||
|
label string
|
||||||
|
reduce func(string) (string, int)
|
||||||
|
}
|
||||||
|
|
||||||
// reduceCommandOutput removes repetitive success-only lines from recognized
|
// reduceCommandOutput removes repetitive success-only lines from recognized
|
||||||
// simple commands. Unsupported or compound shell input passes through exactly,
|
// simple commands. Unsupported or compound shell input passes through exactly,
|
||||||
// and every changed result retains the pre-reduction output as an artifact.
|
// and every changed result retains the pre-reduction output as an artifact.
|
||||||
@@ -16,11 +22,15 @@ func reduceCommandOutput(toolName string, args map[string]any, result Result) Re
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
command, err := commandTextForReducer(toolName, args)
|
command, err := commandTextForReducer(toolName, args)
|
||||||
if err != nil || !isSimpleGoTestCommand(command) {
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
reducer, ok := commandOutputReducerFor(command)
|
||||||
|
if !ok {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
reduced, omitted := reduceGoTestPassingPackages(result.Output)
|
reduced, omitted := reducer.reduce(result.Output)
|
||||||
if omitted < commandReducerMinPassingLines || len(reduced) >= len(result.Output) {
|
if omitted < commandReducerMinPassingLines || len(reduced) >= len(result.Output) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -31,13 +41,14 @@ func reduceCommandOutput(toolName string, args map[string]any, result Result) Re
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
notice := fmt.Sprintf("[zero] compacted %d passing package lines; exact output saved to %s", omitted, spillPath)
|
notice := fmt.Sprintf("[zero] compacted %d %s; exact output saved to %s", omitted, reducer.label, spillPath)
|
||||||
result.Output = strings.TrimRight(reduced, "\n") + "\n" + notice
|
result.Output = strings.TrimRight(reduced, "\n") + "\n" + notice
|
||||||
result.Truncated = true
|
result.Truncated = true
|
||||||
if result.Meta == nil {
|
if result.Meta == nil {
|
||||||
result.Meta = map[string]string{}
|
result.Meta = map[string]string{}
|
||||||
}
|
}
|
||||||
result.Meta["command_output_reduced"] = "true"
|
result.Meta["command_output_reduced"] = "true"
|
||||||
|
result.Meta["command_output_reducer"] = reducer.kind
|
||||||
result.Meta["command_output_omitted_lines"] = strconv.Itoa(omitted)
|
result.Meta["command_output_omitted_lines"] = strconv.Itoa(omitted)
|
||||||
result.Meta["command_output_original_bytes"] = strconv.Itoa(originalBytes)
|
result.Meta["command_output_original_bytes"] = strconv.Itoa(originalBytes)
|
||||||
result.Meta["command_output_original_tokens"] = strconv.Itoa(originalTokens)
|
result.Meta["command_output_original_tokens"] = strconv.Itoa(originalTokens)
|
||||||
@@ -54,23 +65,207 @@ func commandTextForReducer(toolName string, args map[string]any) (string, error)
|
|||||||
return execCommandArg(args)
|
return execCommandArg(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSimpleGoTestCommand(command string) bool {
|
func simpleCommandFields(command string) ([]string, bool) {
|
||||||
command = strings.TrimSpace(command)
|
command = strings.TrimSpace(command)
|
||||||
|
if separator := strings.LastIndexAny(command, " \t"); separator >= 0 && command[separator+1:] == "2>&1" {
|
||||||
|
command = strings.TrimSpace(command[:separator])
|
||||||
|
}
|
||||||
if command == "" || strings.ContainsAny(command, "|;&<>\n\r`") || strings.Contains(command, "$(") {
|
if command == "" || strings.ContainsAny(command, "|;&<>\n\r`") || strings.Contains(command, "$(") {
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
fields := strings.Fields(command)
|
fields := strings.Fields(command)
|
||||||
return len(fields) >= 2 && fields[0] == "go" && fields[1] == "test"
|
if len(fields) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return fields, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandOutputReducerFor(command string) (commandOutputReducer, bool) {
|
||||||
|
fields, ok := simpleCommandFields(command)
|
||||||
|
if !ok {
|
||||||
|
return commandOutputReducer{}, false
|
||||||
|
}
|
||||||
|
if len(fields) >= 2 && fields[0] == "go" && fields[1] == "test" {
|
||||||
|
return commandOutputReducer{kind: "go_test", label: "passing package lines", reduce: reduceGoTestPassingPackages}, true
|
||||||
|
}
|
||||||
|
if len(fields) >= 2 && fields[0] == "cargo" {
|
||||||
|
switch fields[1] {
|
||||||
|
case "test":
|
||||||
|
return commandOutputReducer{kind: "cargo_test", label: "passing Rust test lines", reduce: reduceCargoTestPassingLines}, true
|
||||||
|
case "build", "check":
|
||||||
|
return commandOutputReducer{kind: "cargo_build", label: "successful Rust build progress lines", reduce: reduceCargoBuildProgressLines}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isPytestCommand(fields) {
|
||||||
|
return commandOutputReducer{kind: "pytest", label: "passing pytest progress lines", reduce: reducePytestPassingLines}, true
|
||||||
|
}
|
||||||
|
if isJavaScriptTestCommand(fields) {
|
||||||
|
return commandOutputReducer{kind: "javascript_test", label: "passing JavaScript test-file lines", reduce: reduceJavaScriptTestPassingLines}, true
|
||||||
|
}
|
||||||
|
return commandOutputReducer{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPytestCommand(fields []string) bool {
|
||||||
|
if fields[0] == "pytest" || fields[0] == "py.test" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return len(fields) >= 3 && (fields[0] == "python" || fields[0] == "python3") && fields[1] == "-m" && fields[2] == "pytest"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isJavaScriptTestCommand(fields []string) bool {
|
||||||
|
switch fields[0] {
|
||||||
|
case "jest", "vitest":
|
||||||
|
return true
|
||||||
|
case "npx":
|
||||||
|
return len(fields) >= 2 && (fields[1] == "jest" || fields[1] == "vitest")
|
||||||
|
case "npm", "pnpm", "yarn":
|
||||||
|
if len(fields) >= 2 && fields[1] == "test" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(fields) >= 3 && fields[1] == "run" && fields[2] == "test" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return len(fields) >= 3 && fields[1] == "exec" && (fields[2] == "jest" || fields[2] == "vitest")
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reduceGoTestPassingPackages(output string) (string, int) {
|
func reduceGoTestPassingPackages(output string) (string, int) {
|
||||||
|
return reduceRepeatedOutputLines(output, "passing package lines", func(line string) bool {
|
||||||
|
packageSuccess := strings.HasPrefix(line, "ok \t") || strings.HasPrefix(line, "? \t") ||
|
||||||
|
strings.HasPrefix(line, "ok ") || strings.HasPrefix(line, "? ")
|
||||||
|
return packageSuccess && !strings.Contains(line, "coverage:") && !strings.Contains(line, "[no test files]")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func reduceCargoTestPassingLines(output string) (string, int) {
|
||||||
|
if !strings.Contains(output, "test result:") {
|
||||||
|
return output, 0
|
||||||
|
}
|
||||||
|
return reduceRepeatedOutputLines(output, "passing Rust test lines", func(line string) bool {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
return strings.HasPrefix(trimmed, "test ") && strings.HasSuffix(trimmed, " ... ok")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func reduceCargoBuildProgressLines(output string) (string, int) {
|
||||||
|
if !outputHasCargoFinishedSummary(output) {
|
||||||
|
return output, 0
|
||||||
|
}
|
||||||
|
return reduceRepeatedOutputLines(output, "successful Rust build progress lines", func(line string) bool {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
return strings.HasPrefix(trimmed, "Checking ") || strings.HasPrefix(trimmed, "Compiling ")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func reducePytestPassingLines(output string) (string, int) {
|
||||||
|
if !outputHasPytestSummary(output) {
|
||||||
|
return output, 0
|
||||||
|
}
|
||||||
|
return reduceRepeatedOutputLines(output, "passing pytest progress lines", isPassingPytestProgressLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPassingPytestProgressLine(line string) bool {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
open := strings.LastIndexByte(trimmed, '[')
|
||||||
|
if open < 0 || !strings.HasSuffix(trimmed, "%]") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
progress := strings.TrimSpace(trimmed[:open])
|
||||||
|
fields := strings.Fields(progress)
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
marks := fields[len(fields)-1]
|
||||||
|
return marks != "" && strings.Trim(marks, ".") == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func reduceJavaScriptTestPassingLines(output string) (string, int) {
|
||||||
|
if !outputHasJavaScriptTestSummary(output) {
|
||||||
|
return output, 0
|
||||||
|
}
|
||||||
|
return reduceRepeatedOutputLines(output, "passing JavaScript test-file lines", func(line string) bool {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
return strings.HasPrefix(line, "PASS ") || strings.HasPrefix(trimmed, "✓ ") || strings.HasPrefix(trimmed, "✔ ")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputHasCargoFinishedSummary(output string) bool {
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(trimmed, "Finished `") && strings.Contains(trimmed, "` profile ") &&
|
||||||
|
strings.Contains(trimmed, " target(s) in ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputHasPytestSummary(output string) bool {
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
summary := strings.Trim(strings.TrimSpace(line), "= ")
|
||||||
|
if summary == "short test summary info" || isPytestResultSummary(summary) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPytestResultSummary(summary string) bool {
|
||||||
|
fields := strings.Fields(summary)
|
||||||
|
if len(fields) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
first := strings.Trim(fields[0], ",;:()")
|
||||||
|
if _, err := strconv.Atoi(first); err != nil || !summaryHasCountedStatus(summary, "passed") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for index := 1; index+1 < len(fields); index++ {
|
||||||
|
if fields[index] != "in" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
duration := strings.TrimSuffix(strings.Trim(fields[index+1], ",;:()"), "s")
|
||||||
|
if _, err := strconv.ParseFloat(duration, 64); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputHasJavaScriptTestSummary(output string) bool {
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(trimmed, "Test Suites:") && summaryHasCountedStatus(trimmed, "passed") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(trimmed, "Test Files ") && summaryHasCountedStatus(trimmed, "passed") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func summaryHasCountedStatus(summary string, status string) bool {
|
||||||
|
fields := strings.Fields(summary)
|
||||||
|
for index := 1; index < len(fields); index++ {
|
||||||
|
if strings.Trim(fields[index], ",;:()") != status {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count := strings.Trim(fields[index-1], ",;:()")
|
||||||
|
if _, err := strconv.Atoi(count); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func reduceRepeatedOutputLines(output string, label string, omit func(string) bool) (string, int) {
|
||||||
lines := strings.Split(output, "\n")
|
lines := strings.Split(output, "\n")
|
||||||
kept := make([]string, 0, len(lines))
|
kept := make([]string, 0, len(lines))
|
||||||
omitted := 0
|
omitted := 0
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
packageSuccess := strings.HasPrefix(line, "ok \t") || strings.HasPrefix(line, "? \t") ||
|
if omit(line) {
|
||||||
strings.HasPrefix(line, "ok ") || strings.HasPrefix(line, "? ")
|
|
||||||
if packageSuccess && !strings.Contains(line, "coverage:") && !strings.Contains(line, "[no test files]") {
|
|
||||||
omitted++
|
omitted++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -87,7 +282,7 @@ func reduceGoTestPassingPackages(output string) (string, int) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
summary := fmt.Sprintf("[zero] %d passing package lines omitted", omitted)
|
summary := fmt.Sprintf("[zero] %d %s omitted", omitted, label)
|
||||||
kept = append(kept, "")
|
kept = append(kept, "")
|
||||||
copy(kept[insertAt+1:], kept[insertAt:])
|
copy(kept[insertAt+1:], kept[insertAt:])
|
||||||
kept[insertAt] = summary
|
kept[insertAt] = summary
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commandReducerCorpusCase struct {
|
||||||
|
name string
|
||||||
|
command string
|
||||||
|
output string
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandReducerCorpus() []commandReducerCorpusCase {
|
||||||
|
goLines := []string{"output:"}
|
||||||
|
cargoTestLines := []string{"output:", "running 24 tests"}
|
||||||
|
cargoCheckLines := []string{"output:"}
|
||||||
|
pytestLines := []string{"output:"}
|
||||||
|
vitestLines := []string{"output:", " RUN v3.2.4 /workspace"}
|
||||||
|
jestLines := []string{"output:"}
|
||||||
|
for index := 0; index < 24; index++ {
|
||||||
|
goLines = append(goLines, fmt.Sprintf("ok \texample.test/pkg%d\t0.01s", index))
|
||||||
|
cargoTestLines = append(cargoTestLines, fmt.Sprintf("test module::case_%02d ... ok", index))
|
||||||
|
cargoCheckLines = append(cargoCheckLines, fmt.Sprintf(" Checking dependency_%02d v1.0.%d", index, index))
|
||||||
|
pytestLines = append(pytestLines, fmt.Sprintf("tests/test_module_%02d.py ........ [%3d%%]", index, (index+1)*100/24))
|
||||||
|
vitestLines = append(vitestLines, fmt.Sprintf(" ✓ src/module_%02d.test.ts (8 tests) 12ms", index))
|
||||||
|
jestLines = append(jestLines, fmt.Sprintf("PASS src/module_%02d.test.ts", index))
|
||||||
|
}
|
||||||
|
goLines = append(goLines, "exit_code: 0")
|
||||||
|
cargoTestLines = append(cargoTestLines,
|
||||||
|
"test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s",
|
||||||
|
"exit_code: 0",
|
||||||
|
)
|
||||||
|
cargoCheckLines = append(cargoCheckLines,
|
||||||
|
" Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.41s",
|
||||||
|
"exit_code: 0",
|
||||||
|
)
|
||||||
|
pytestLines = append(pytestLines,
|
||||||
|
"============================= 192 passed in 3.21s =============================",
|
||||||
|
"exit_code: 0",
|
||||||
|
)
|
||||||
|
vitestLines = append(vitestLines,
|
||||||
|
" Test Files 24 passed (24)",
|
||||||
|
" Tests 192 passed (192)",
|
||||||
|
" Duration 3.21s",
|
||||||
|
"exit_code: 0",
|
||||||
|
)
|
||||||
|
jestLines = append(jestLines,
|
||||||
|
"Test Suites: 24 passed, 24 total",
|
||||||
|
"Tests: 192 passed, 192 total",
|
||||||
|
"Time: 3.21 s",
|
||||||
|
"exit_code: 0",
|
||||||
|
)
|
||||||
|
return []commandReducerCorpusCase{
|
||||||
|
{name: "go_test", command: "go test ./...", output: strings.Join(goLines, "\n"), want: "exit_code: 0"},
|
||||||
|
{name: "cargo_test", command: "cargo test --workspace", output: strings.Join(cargoTestLines, "\n"), want: "24 passed"},
|
||||||
|
{name: "cargo_check", command: "cargo check --workspace", output: strings.Join(cargoCheckLines, "\n"), want: "Finished `dev` profile"},
|
||||||
|
{name: "pytest", command: "pytest -q", output: strings.Join(pytestLines, "\n"), want: "192 passed"},
|
||||||
|
{name: "npm_vitest", command: "npm test", output: strings.Join(vitestLines, "\n"), want: "192 passed"},
|
||||||
|
{name: "pnpm_vitest", command: "pnpm test", output: strings.Join(vitestLines, "\n"), want: "192 passed"},
|
||||||
|
{name: "yarn_jest", command: "yarn test", output: strings.Join(jestLines, "\n"), want: "192 passed"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandOutputReductionCorpusMetrics(t *testing.T) {
|
||||||
|
t.Setenv("TMPDIR", t.TempDir())
|
||||||
|
totalRawTokens := 0
|
||||||
|
totalModelTokens := 0
|
||||||
|
for _, testCase := range commandReducerCorpus() {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": testCase.command}, Result{
|
||||||
|
Status: StatusOK,
|
||||||
|
Output: testCase.output,
|
||||||
|
})
|
||||||
|
if !strings.Contains(result.Output, testCase.want) {
|
||||||
|
t.Fatalf("decisive summary %q was lost: %q", testCase.want, result.Output)
|
||||||
|
}
|
||||||
|
if spillPath := result.Meta["spill_path"]; spillPath != "" {
|
||||||
|
spilled, err := os.ReadFile(spillPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read raw artifact: %v", err)
|
||||||
|
}
|
||||||
|
if string(spilled) != testCase.output {
|
||||||
|
t.Fatal("raw artifact differs from original output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rawTokens := estimateOutputTokens(testCase.output)
|
||||||
|
modelTokens := estimateOutputTokens(result.Output)
|
||||||
|
totalRawTokens += rawTokens
|
||||||
|
totalModelTokens += modelTokens
|
||||||
|
reduction := 100 * (rawTokens - modelTokens) / rawTokens
|
||||||
|
t.Logf("raw_bytes=%d model_bytes=%d raw_tokens=%d model_tokens=%d reduction_pct=%d reduced=%t",
|
||||||
|
len(testCase.output), len(result.Output), rawTokens, modelTokens, reduction, result.Meta["command_output_reduced"] == "true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if totalRawTokens == 0 {
|
||||||
|
t.Fatal("empty reducer corpus")
|
||||||
|
}
|
||||||
|
if totalModelTokens >= totalRawTokens {
|
||||||
|
t.Fatalf("aggregate reducer output did not save tokens: raw=%d model=%d", totalRawTokens, totalModelTokens)
|
||||||
|
}
|
||||||
|
t.Logf("aggregate raw_tokens=%d model_tokens=%d reduction_pct=%d",
|
||||||
|
totalRawTokens, totalModelTokens, 100*(totalRawTokens-totalModelTokens)/totalRawTokens)
|
||||||
|
}
|
||||||
@@ -1,12 +1,32 @@
|
|||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type commandReducerFixtureTool struct {
|
||||||
|
name string
|
||||||
|
output string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tool commandReducerFixtureTool) Name() string { return tool.name }
|
||||||
|
func (tool commandReducerFixtureTool) Description() string {
|
||||||
|
return "returns command output for reducer integration tests"
|
||||||
|
}
|
||||||
|
func (tool commandReducerFixtureTool) Parameters() Schema {
|
||||||
|
return Schema{Type: "object", AdditionalProperties: true}
|
||||||
|
}
|
||||||
|
func (tool commandReducerFixtureTool) Safety() Safety {
|
||||||
|
return Safety{SideEffect: SideEffectRead, Permission: PermissionAllow, Reason: "test output"}
|
||||||
|
}
|
||||||
|
func (tool commandReducerFixtureTool) Run(context.Context, map[string]any) Result {
|
||||||
|
return Result{Status: StatusOK, Output: tool.output}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReduceCommandOutputCompactsPassingGoPackagesAndKeepsRawArtifact(t *testing.T) {
|
func TestReduceCommandOutputCompactsPassingGoPackagesAndKeepsRawArtifact(t *testing.T) {
|
||||||
var lines []string
|
var lines []string
|
||||||
lines = append(lines, "output:")
|
lines = append(lines, "output:")
|
||||||
@@ -131,3 +151,167 @@ func TestSelfManagedBudgetPreservesRawSpillWhenReducedOutputAlsoTruncates(t *tes
|
|||||||
t.Fatalf("raw spill was not preserved: err=%v", err)
|
t.Fatalf("raw spill was not preserved: err=%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReduceCommandOutputCompactsRecognizedTestAndBuildNoise(t *testing.T) {
|
||||||
|
t.Setenv("TMPDIR", t.TempDir())
|
||||||
|
for _, testCase := range commandReducerCorpus() {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": testCase.command}, Result{
|
||||||
|
Status: StatusOK,
|
||||||
|
Output: testCase.output,
|
||||||
|
})
|
||||||
|
if result.Meta["command_output_reduced"] != "true" {
|
||||||
|
t.Fatalf("recognized command output was not reduced: %#v", result)
|
||||||
|
}
|
||||||
|
if !strings.Contains(result.Output, testCase.want) || !strings.Contains(result.Output, "exit_code: 0") {
|
||||||
|
t.Fatalf("decisive summary or exit status was lost: %q", result.Output)
|
||||||
|
}
|
||||||
|
spillPath := result.Meta["spill_path"]
|
||||||
|
spilled, err := os.ReadFile(spillPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read raw artifact: %v", err)
|
||||||
|
}
|
||||||
|
if string(spilled) != testCase.output {
|
||||||
|
t.Fatal("raw artifact differs from original output")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduceCommandOutputPreservesFailureEvidence(t *testing.T) {
|
||||||
|
t.Setenv("TMPDIR", t.TempDir())
|
||||||
|
passing := make([]string, 0, commandReducerMinPassingLines+2)
|
||||||
|
for index := 0; index < commandReducerMinPassingLines+2; index++ {
|
||||||
|
passing = append(passing, fmt.Sprintf("test module::case_%02d ... ok", index))
|
||||||
|
}
|
||||||
|
original := strings.Join(append(passing,
|
||||||
|
"test module::broken ... FAILED",
|
||||||
|
"failures:",
|
||||||
|
"---- module::broken stdout ----",
|
||||||
|
"assertion failed: expected zero",
|
||||||
|
"test result: FAILED. 14 passed; 1 failed; finished in 0.08s",
|
||||||
|
"exit_code: 101",
|
||||||
|
), "\n")
|
||||||
|
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": "cargo test --workspace"}, Result{
|
||||||
|
Status: StatusError,
|
||||||
|
Output: original,
|
||||||
|
})
|
||||||
|
for _, evidence := range []string{"module::broken ... FAILED", "assertion failed: expected zero", "14 passed; 1 failed", "exit_code: 101"} {
|
||||||
|
if !strings.Contains(result.Output, evidence) {
|
||||||
|
t.Fatalf("failure evidence %q was lost: %q", evidence, result.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result.Meta["command_output_reduced"] != "true" {
|
||||||
|
t.Fatalf("repetitive passing output was not reduced: %#v", result)
|
||||||
|
}
|
||||||
|
spillPath := result.Meta["spill_path"]
|
||||||
|
if spillPath == "" {
|
||||||
|
t.Fatal("reduced failure output did not retain a raw artifact")
|
||||||
|
}
|
||||||
|
spilled, err := os.ReadFile(spillPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read raw artifact: %v", err)
|
||||||
|
}
|
||||||
|
if string(spilled) != original {
|
||||||
|
t.Fatal("raw artifact differs from original output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduceCommandOutputPassesThroughUnsupportedAndCompoundCommands(t *testing.T) {
|
||||||
|
outputs := make(map[string]string)
|
||||||
|
for _, fixture := range commandReducerCorpus() {
|
||||||
|
outputs[fixture.name] = fixture.output
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
command string
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{command: "npm test | tee test.log", output: outputs["npm_vitest"]},
|
||||||
|
{command: "cargo test && echo done", output: outputs["cargo_test"]},
|
||||||
|
{command: "pytest; notify-send done", output: outputs["pytest"]},
|
||||||
|
{command: "cargo test 1>test.log", output: outputs["cargo_test"]},
|
||||||
|
{command: "cargo test 2>>errors.log", output: outputs["cargo_test"]},
|
||||||
|
{command: "git status --short", output: outputs["npm_vitest"]},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": testCase.command}, Result{Status: StatusOK, Output: testCase.output})
|
||||||
|
if result.Output != testCase.output || result.Meta != nil {
|
||||||
|
t.Fatalf("command %q should pass through exactly, got %#v", testCase.command, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduceCommandOutputAllowsTrailingStderrMerge(t *testing.T) {
|
||||||
|
t.Setenv("TMPDIR", t.TempDir())
|
||||||
|
fixture := commandReducerCorpus()[1]
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": fixture.command + " 2>&1"}, Result{
|
||||||
|
Status: StatusOK,
|
||||||
|
Output: fixture.output,
|
||||||
|
})
|
||||||
|
if result.Meta["command_output_reducer"] != "cargo_test" {
|
||||||
|
t.Fatalf("trailing stderr merge prevented reduction: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduceCommandOutputRequiresAuthoritativeRunnerSummary(t *testing.T) {
|
||||||
|
lookalikes := map[string]string{
|
||||||
|
"cargo test": "test user::message ... ok",
|
||||||
|
"cargo check": "Checking a custom deployment state",
|
||||||
|
"pytest": "application log ........ [100%]",
|
||||||
|
"npm test": "PASS user-provided log message",
|
||||||
|
}
|
||||||
|
for command, line := range lookalikes {
|
||||||
|
original := strings.Repeat(line+"\n", commandReducerMinPassingLines+2) + "exit_code: 0"
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": command}, Result{Status: StatusOK, Output: original})
|
||||||
|
if result.Output != original || result.Meta != nil {
|
||||||
|
t.Fatalf("summary-free output for %q should pass through, got %#v", command, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReduceCommandOutputRejectsLookalikeRunnerSummaries(t *testing.T) {
|
||||||
|
t.Setenv("TMPDIR", t.TempDir())
|
||||||
|
testCases := []struct {
|
||||||
|
command string
|
||||||
|
line string
|
||||||
|
summary string
|
||||||
|
}{
|
||||||
|
{command: "cargo check", line: "Compiling dependency v1.0.0", summary: "Finished generating bindings"},
|
||||||
|
{command: "pytest", line: "tests/test_module.py ........ [100%]", summary: "application log: 14 passed validation in staging"},
|
||||||
|
{command: "npm test", line: "PASS src/example.test.ts", summary: "Test Files discovered: 14"},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
original := strings.Repeat(testCase.line+"\n", commandReducerMinPassingLines+2) + testCase.summary + "\nexit_code: 1"
|
||||||
|
result := reduceCommandOutput(ExecCommandToolName, map[string]any{"cmd": testCase.command}, Result{Status: StatusError, Output: original})
|
||||||
|
if result.Output != original || result.Meta != nil {
|
||||||
|
t.Fatalf("lookalike summary for %q should pass through exactly, got %#v", testCase.command, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistryAppliesCommandReducerToExecAndBashTools(t *testing.T) {
|
||||||
|
t.Setenv("TMPDIR", t.TempDir())
|
||||||
|
fixture := commandReducerCorpus()[1]
|
||||||
|
for _, toolName := range []string{ExecCommandToolName, "bash"} {
|
||||||
|
t.Run(toolName, func(t *testing.T) {
|
||||||
|
registry := NewRegistry()
|
||||||
|
registry.Register(commandReducerFixtureTool{name: toolName, output: fixture.output})
|
||||||
|
args := map[string]any{"cmd": fixture.command}
|
||||||
|
if toolName == "bash" {
|
||||||
|
args = map[string]any{"command": fixture.command}
|
||||||
|
}
|
||||||
|
result := registry.Run(context.Background(), toolName, args)
|
||||||
|
if result.Status != StatusOK || result.Meta["command_output_reducer"] != "cargo_test" {
|
||||||
|
t.Fatalf("registry result was not reduced: %#v", result)
|
||||||
|
}
|
||||||
|
if !strings.Contains(result.Output, fixture.want) {
|
||||||
|
t.Fatalf("registry reduction lost summary: %q", result.Output)
|
||||||
|
}
|
||||||
|
spilled, err := os.ReadFile(result.Meta["spill_path"])
|
||||||
|
if err != nil || string(spilled) != fixture.output {
|
||||||
|
t.Fatalf("registry raw artifact mismatch: err=%v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user