mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-10 10:10:49 +00:00
bdb60de7ae
- 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
119 lines
3.4 KiB
Go
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")
|
|
}
|
|
}
|