返回 DeepSeek-Reasonix
agent_config_test.go
根目录 / internal / agent / agent_config_test.go
1 package agent
2
3 import (
4 "go/ast"
5 "go/parser"
6 "go/token"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11 )
12
13 // configFieldNames is agentConfig's surface, read from the type itself so the
14 // guard below cannot drift from what the struct actually holds.
15 func configFieldNames(t *testing.T) map[string]bool {
16 t.Helper()
17 fset := token.NewFileSet()
18 file, err := parser.ParseFile(fset, "agent_config.go", nil, 0)
19 if err != nil {
20 t.Fatalf("parse agent_config.go: %v", err)
21 }
22 names := map[string]bool{}
23 ast.Inspect(file, func(n ast.Node) bool {
24 spec, ok := n.(*ast.TypeSpec)
25 if !ok || spec.Name.Name != "agentConfig" {
26 return true
27 }
28 st, ok := spec.Type.(*ast.StructType)
29 if !ok {
30 return false
31 }
32 for _, field := range st.Fields.List {
33 for _, name := range field.Names {
34 names[name.Name] = true
35 }
36 }
37 return false
38 })
39 if len(names) == 0 {
40 t.Fatal("agentConfig has no fields; the guard would pass vacuously")
41 }
42 return names
43 }
44
45 // Field promotion makes `a.contextWindow = x` compile from anywhere in the
46 // package, so "configuration" is a claim rather than a guarantee. That claim is
47 // what lets the struct-state ratchet exclude these fields; unenforced, the
48 // exclusion would just be a way to hide state. So it is checked, not asserted.
49 func TestAgentConfigIsNeverAssignedAfterConstruction(t *testing.T) {
50 fields := configFieldNames(t)
51 entries, err := os.ReadDir(".")
52 if err != nil {
53 t.Fatalf("read package dir: %v", err)
54 }
55 fset := token.NewFileSet()
56 for _, entry := range entries {
57 name := entry.Name()
58 if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
59 continue
60 }
61 file, err := parser.ParseFile(fset, filepath.Join(".", name), nil, 0)
62 if err != nil {
63 t.Fatalf("parse %s: %v", name, err)
64 }
65 ast.Inspect(file, func(n ast.Node) bool {
66 assign, ok := n.(*ast.AssignStmt)
67 if !ok {
68 return true
69 }
70 for _, lhs := range assign.Lhs {
71 sel, ok := lhs.(*ast.SelectorExpr)
72 if !ok || !fields[sel.Sel.Name] {
73 continue
74 }
75 // Only Agent's own receiver: other types (TaskTool) legitimately
76 // carry same-named fields of their own.
77 recv, ok := sel.X.(*ast.Ident)
78 if !ok || recv.Name != "a" {
79 continue
80 }
81 t.Errorf("%s:%d: %s.%s is assigned after construction; agentConfig must stay immutable, or the field belongs on Agent",
82 name, fset.Position(sel.Pos()).Line, recv.Name, sel.Sel.Name)
83 }
84 return true
85 })
86 }
87 }
88
88 lines GO