Files
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

202 lines
5.4 KiB
Go

package agent
import (
"reflect"
"testing"
)
func TestClassifyToolCommand_UnwrapsShellAndClassifiesVerbs(t *testing.T) {
cases := []struct {
name string
command string
want []ToolCategory
}{
{
name: "read through zsh wrapper",
command: "/run/current-system/sw/bin/zsh -lc 'cat file.txt'",
want: []ToolCategory{ToolRead},
},
{
name: "grep is read",
command: "bash -lc 'grep -rn foo internal/'",
want: []ToolCategory{ToolRead},
},
{
name: "go test is test_lint",
command: "bash -lc 'go test ./...'",
want: []ToolCategory{ToolTestLint},
},
{
name: "go build is other",
command: "bash -lc 'go build ./cmd/x'",
want: []ToolCategory{ToolOther},
},
{
name: "git is git",
command: "zsh -lc 'git commit -m wip'",
want: []ToolCategory{ToolGit},
},
{
name: "apply_patch is edit",
command: "bash -lc 'apply_patch <<EOF'",
want: []ToolCategory{ToolEdit},
},
{
name: "sed in place is edit",
command: "bash -lc 'sed -i.bak s/a/b/ f'",
want: []ToolCategory{ToolEdit},
},
{
name: "sed non-inplace is read",
command: "bash -lc 'sed -n 1,5p f'",
want: []ToolCategory{ToolRead},
},
{
name: "sleep is wait",
command: "bash -lc 'sleep 5'",
want: []ToolCategory{ToolWait},
},
{
name: "codex write_stdin poll is wait",
command: "write_stdin",
want: []ToolCategory{ToolWait},
},
{
name: "shellcheck is test_lint",
command: "bash -lc 'shellcheck bin/*.sh'",
want: []ToolCategory{ToolTestLint},
},
{
name: "make lint is test_lint, make build is other",
command: "bash -lc 'make lint'",
want: []ToolCategory{ToolTestLint},
},
{
name: "npm run lint is test_lint",
command: "bash -lc 'npm run lint'",
want: []ToolCategory{ToolTestLint},
},
{
name: "env-var prefix is skipped",
command: "bash -lc 'FOO=bar CGO_ENABLED=0 go vet ./...'",
want: []ToolCategory{ToolTestLint},
},
{
name: "sudo wrapper is skipped",
command: "bash -lc 'sudo rm -rf build'",
want: []ToolCategory{ToolEdit},
},
{
name: "compound command classifies each sub-command",
command: "bash -lc 'go test ./... && git commit -am wip && cat out.txt'",
want: []ToolCategory{ToolTestLint, ToolGit, ToolRead},
},
{
name: "pipe splits sub-commands",
command: "bash -lc 'cat log | grep err'",
want: []ToolCategory{ToolRead, ToolRead},
},
{
name: "quoted operators stay within one sub-command",
command: `rg 'foo|bar;baz'`,
want: []ToolCategory{ToolRead},
},
{
name: "escaped operators stay within one sub-command",
command: `printf a\|b\;c && git status`,
want: []ToolCategory{ToolOther, ToolGit},
},
{
name: "logical or splits sub-commands",
command: `rg foo || printf missing`,
want: []ToolCategory{ToolRead, ToolOther},
},
{
name: "no shell wrapper classifies directly",
command: "git status",
want: []ToolCategory{ToolGit},
},
{
name: "unknown verb is other",
command: "bash -lc 'curl https://example.com'",
want: []ToolCategory{ToolOther},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := ClassifyToolCommand(tc.command)
if !reflect.DeepEqual(got, tc.want) {
t.Fatalf("ClassifyToolCommand(%q) = %v, want %v", tc.command, got, tc.want)
}
})
}
}
func TestToolCategoryCounts_AddAndTotal(t *testing.T) {
var c ToolCategoryCounts
for _, cat := range []ToolCategory{ToolWait, ToolTestLint, ToolTestLint, ToolEdit, ToolRead, ToolGit, ToolOther, "unrecognized"} {
c.Add(cat)
}
want := ToolCategoryCounts{Wait: 1, TestLint: 2, Edit: 1, Read: 1, Git: 1, Other: 2}
if c != want {
t.Fatalf("counts = %+v, want %+v", c, want)
}
if c.Total() != 8 {
t.Fatalf("Total() = %d, want 8", c.Total())
}
}
func TestModelTimeMS(t *testing.T) {
cases := []struct{ duration, wait, want int64 }{
{1000, 0, 1000},
{1000, 300, 700},
{1000, 1000, 0},
{1000, 1500, 0},
{500, -5, 500},
}
for _, tc := range cases {
if got := ModelTimeMS(tc.duration, tc.wait); got != tc.want {
t.Fatalf("ModelTimeMS(%d,%d) = %d, want %d", tc.duration, tc.wait, got, tc.want)
}
}
}
func TestFreshInputTokens(t *testing.T) {
cases := []struct{ input, cache, want int }{
{1000, 0, 1000},
{1000, 800, 200},
{1000, 1000, 0},
{1000, 1200, 0},
}
for _, tc := range cases {
if got := FreshInputTokens(tc.input, tc.cache); got != tc.want {
t.Fatalf("FreshInputTokens(%d,%d) = %d, want %d", tc.input, tc.cache, got, tc.want)
}
}
}
// TestPerRoundTokens covers the cumulative-vs-per-invocation delta rule that
// keeps a resumed session's cumulative counter from being mistaken for
// per-round usage.
func TestPerRoundTokens(t *testing.T) {
cases := []struct {
name string
current int
prior int
cumulative bool
want int
}{
{"cumulative resumed round subtracts prior", 1_280_000, 578_000, true, 702_000},
{"cumulative first round has no prior", 578_000, 0, true, 578_000},
{"non-cumulative reports current", 702_000, 578_000, false, 702_000},
{"cumulative shrink treated as per-round", 400_000, 578_000, true, 400_000},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := PerRoundTokens(tc.current, tc.prior, tc.cumulative); got != tc.want {
t.Fatalf("PerRoundTokens(%d,%d,%v) = %d, want %d", tc.current, tc.prior, tc.cumulative, got, tc.want)
}
})
}
}