Repository navigation
Expand file tree
/
Copy pathchatv3_guard_class_test.go
More file actions
128 lines (124 loc) · 6.34 KB
/
Copy pathchatv3_guard_class_test.go
File metadata and controls
128 lines (124 loc) · 6.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package main
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"sort"
"strings"
"testing"
)
// TestV3ProcessGuardGoClass is the inventory law for goroutines started by a
// v3 process or launch. A long-lived writer must identify both its stop and the
// closeAll join, or the clock-owned equivalent before its store closes.
// chatv3/models and engine/models name the process waiter, joined
// since #1179, and the internal/catalog warm each one starts, which closeAll now
// cancels and joins through Models.Close. chatv3/sweep-home is joined too, by
// stopPlaceSweep since #1276. No row is known-open now; a new one that is must
// name the later cell that owns it, and deleting it is part of landing that cell.
// C29.2/C30.4 add surface-owned writers with cancellation and explicit joins.
// C25 removes the budget worker entirely: opening a chat starts no collection.
func TestV3ProcessGuardGoClass(t *testing.T) {
type class struct {
kind, stop, join, owner string
}
want := map[string]class{
"knowledge/background": {kind: "joined writer", stop: "v3Process.processStop cancels the knowledge worker and its provider requests", join: "v3Process.closeAll waits on knowledgeDone before closing process stores"},
"pool/judge-sweep": {kind: "joined writer", stop: "pool errand context", join: "v3Process.closeAll calls stopPoolErrands"},
"chatv3/models": {kind: "joined writer", stop: "warmModels waiter on the pool errand context, and the catalog's own warm context", join: "v3Process.closeAll calls stopPoolErrands for the waiter (since #1179) and Models.Close, which cancels and joins the internal/catalog warm"},
"engine/models": {kind: "joined writer", stop: "warmModels waiter on the pool errand context, and the catalog's own warm context", join: "v3Process.closeAll calls stopPoolErrands for the waiter (since #1179) and Models.Close, which cancels and joins the internal/catalog warm"},
"chatv3/sweep-home": {kind: "joined writer", stop: "v3Process.sweepCancel cancels the walk's context, checked between entries and before every destructive operation", join: "v3Process.closeAll calls stopPlaceSweep, which waits on sweepDone (since #1276)"},
"chatv3/once-questions": {kind: "one-shot", stop: "agent.Close closes the WatchQuestions channel consumed by the range"},
"chatv3/close-agent": {kind: "joined one-shot", stop: "Agent.Close is bounded", join: "v3Process.closeAll waits on waiting"},
"chatv3/payment-credits": {kind: "joined one-shot", stop: "processCtx is canceled and credits.Read is bounded", join: "v3Process.closeAll calls creditWatcher.close and waits"},
"chatv3/retired-receipts": {kind: "one-shot", stop: "waitV3Receipts bounds its wait on the closed agent's admitted receipts"},
"chatv3/host-reap": {kind: "one-shot", stop: "exec.Cmd.Wait returns when the replaced ssh child exits"},
"chatv3/host-follow": {kind: "one-shot", stop: "remote Follow channel closes with the host connection"},
"chatv3/telemetry-events": {kind: "one-shot", stop: "source event channel closes and countedEvents returns"},
"sync/home-state": {kind: "joined writer", stop: "surface stateCtx is cancelled on exit", join: "runSurface waits on stateDone before returning"},
"sync/older-chats": {kind: "joined writer", stop: "surface stateCtx is cancelled on exit", join: "runSurface waits on copiesDone before returning"},
"memory/maintenance": {kind: "joined writer", stop: "startClockMemoryMaintenance cancels its child of the clock context", join: "runClock defers stopMemoryMaintenance, which cancels and waits on done before the earlier openV3ClockProcess stop defer closes proc.Memory; TestClockMemoryMaintenanceStopsBeforeStoreCloses proves the join"},
"automations/retire": {kind: "one-shot", stop: "the clock's own context ends when `codeaf clock` leaves, or the watcher cancels it on finding the binary replaced"},
}
got := processGuardScopes(t)
for scope, pos := range got {
c, ok := want[scope]
if !ok {
t.Errorf("new v3 process guard.Go %q at %s is unclassified: name its stop and closeAll join, or add a KNOWN-OPEN owner", scope, pos)
continue
}
if strings.Contains(c.kind, "KNOWN-OPEN") {
if c.owner == "" {
t.Errorf("%s: KNOWN-OPEN entry has no owning later cell", scope)
}
} else if strings.Contains(c.kind, "joined") && (c.stop == "" || c.join == "") {
t.Errorf("%s: joined entry must name both stop and closeAll join", scope)
}
delete(want, scope)
}
if len(want) != 0 {
missing := make([]string, 0, len(want))
for scope := range want {
missing = append(missing, scope)
}
sort.Strings(missing)
t.Fatalf("classified v3 process guards disappeared from the source inventory: %v", missing)
}
}
func processGuardScopes(t *testing.T) map[string]string {
t.Helper()
entries, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
files := []string{"engine.go", "telemetry_events.go"}
for _, entry := range entries {
name := entry.Name()
if strings.HasPrefix(name, "chatv3") && strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") {
files = append(files, name)
}
}
fset := token.NewFileSet()
out := make(map[string]string)
for _, name := range files {
file, err := parser.ParseFile(fset, name, nil, 0)
if err != nil {
t.Fatalf("parse %s: %v", name, err)
}
ast.Inspect(file, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok || len(call.Args) == 0 {
return true
}
if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "poolErrandGoCtx" {
lit, ok := call.Args[1].(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
return true
}
out[strings.Trim(lit.Value, "\\\"")] = filepath.ToSlash(fset.Position(call.Pos()).String())
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
pkg, ok := sel.X.(*ast.Ident)
if !ok || (pkg.Name == "guard" && sel.Sel.Name != "Go") || (pkg.Name == "proc" && sel.Sel.Name != "warmModels") {
return true
}
if pkg.Name != "guard" && pkg.Name != "proc" {
return true
}
lit, ok := call.Args[0].(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
return true // generic helpers are classified at their literal call sites
}
scope := strings.Trim(lit.Value, "\"")
pos := fset.Position(call.Pos())
out[scope] = filepath.ToSlash(pos.String())
return true
})
}
return out
}