Files
goclaw/internal/agent/toolloop_test.go
T
viettranx bdb60de7ae chore: upgrade Go 1.25 → 1.26 and apply go fix modernizations
- Update go.mod and Dockerfile to Go 1.26
- Apply `go fix ./...` stdlib modernizations across 170+ files
- Add `go fix` to post-implementation checklist in CLAUDE.md
- Fix go fix misapplied rewrite in loop_history.go
2026-03-10 00:09:15 +07:00

119 lines
3.4 KiB
Go

package agent
import "testing"
func TestToolLoopDetection_NoLoop(t *testing.T) {
var s toolLoopState
// 2 identical calls with same result → below threshold, no detection
for i := range 2 {
h := s.record("list_files", map[string]any{"path": "."})
s.recordResult(h, "access denied")
level, _ := s.detect("list_files", h)
if level != "" {
t.Fatalf("iteration %d: expected no detection, got %q", i, level)
}
}
}
func TestToolLoopDetection_Warning(t *testing.T) {
var s toolLoopState
var lastLevel string
for range toolLoopWarningThreshold {
h := s.record("list_files", map[string]any{"path": "."})
s.recordResult(h, "access denied")
lastLevel, _ = s.detect("list_files", h)
}
if lastLevel != "warning" {
t.Fatalf("expected warning after %d calls, got %q", toolLoopWarningThreshold, lastLevel)
}
}
func TestToolLoopDetection_Critical(t *testing.T) {
var s toolLoopState
var lastLevel string
for range toolLoopCriticalThreshold {
h := s.record("list_files", map[string]any{"path": "."})
s.recordResult(h, "access denied")
lastLevel, _ = s.detect("list_files", h)
}
if lastLevel != "critical" {
t.Fatalf("expected critical after %d calls, got %q", toolLoopCriticalThreshold, lastLevel)
}
}
func TestToolLoopDetection_DifferentArgs(t *testing.T) {
var s toolLoopState
// Same tool but different args each time → no detection
for i := range 15 {
args := map[string]any{"path": string(rune('a' + i))}
h := s.record("list_files", args)
s.recordResult(h, "access denied")
level, _ := s.detect("list_files", h)
if level != "" {
t.Fatalf("iteration %d: expected no detection for different args, got %q", i, level)
}
}
}
func TestToolLoopDetection_DifferentResults(t *testing.T) {
var s toolLoopState
// Same args but different results each time → progress, no detection
for i := range 15 {
h := s.record("web_fetch", map[string]any{"url": "https://example.com"})
s.recordResult(h, "result content "+string(rune('a'+i)))
level, _ := s.detect("web_fetch", h)
if level != "" {
t.Fatalf("iteration %d: expected no detection for different results, got %q", i, level)
}
}
}
func TestToolLoopDetection_MixedTools(t *testing.T) {
var s toolLoopState
// Alternate between two tools with same result → each tool only hit ~half
// With 8 iterations, each tool is called 4 times → below critical (5)
for i := range 8 {
toolName := "list_files"
if i%2 == 1 {
toolName = "read_file"
}
h := s.record(toolName, map[string]any{"path": "."})
s.recordResult(h, "error")
level, _ := s.detect(toolName, h)
// Each tool is only called 4 times, should at most warn
if level == "critical" {
t.Fatalf("iteration %d: unexpected critical for alternating tools", i)
}
}
}
func TestStableJSON(t *testing.T) {
// Same keys in different order → same hash
a := stableJSON(map[string]any{"b": 2, "a": 1})
b := stableJSON(map[string]any{"a": 1, "b": 2})
if a != b {
t.Fatalf("stableJSON not deterministic: %q != %q", a, b)
}
}
func TestHashToolCall(t *testing.T) {
// Same input → same hash
h1 := hashToolCall("list_files", map[string]any{"path": "."})
h2 := hashToolCall("list_files", map[string]any{"path": "."})
if h1 != h2 {
t.Fatal("hashToolCall not deterministic")
}
// Different tool → different hash
h3 := hashToolCall("read_file", map[string]any{"path": "."})
if h1 == h3 {
t.Fatal("different tools should have different hashes")
}
}