返回 DeepSeek-Reasonix
catalog_snapshot_test.go
根目录 / internal / skill / catalog_snapshot_test.go
1 package skill
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "runtime"
9 "sync"
10 "testing"
11 "time"
12 )
13
14 func TestCatalogSnapshotColdCallersShareOneScanAndWarmReadsDoNotScan(t *testing.T) {
15 root := t.TempDir()
16 for i := range 1154 {
17 path := filepath.Join(root, fmt.Sprintf("skill-%04d", i), SkillFile)
18 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
19 t.Fatal(err)
20 }
21 if err := os.WriteFile(path, fmt.Appendf(nil, "---\ndescription: skill %d\n---\nbody", i), 0o644); err != nil {
22 t.Fatal(err)
23 }
24 }
25 store := New(Options{HomeDir: t.TempDir(), CustomPaths: []string{root}, DisableBuiltins: true})
26 t.Cleanup(func() { _ = store.Close() })
27
28 const callers = 12
29 start := make(chan struct{})
30 var wg sync.WaitGroup
31 wg.Add(callers)
32 for range callers {
33 go func() {
34 defer wg.Done()
35 <-start
36 snapshot, err := store.Snapshot(context.Background())
37 if err != nil || len(snapshot.Candidates) != 1154 || !snapshot.Complete {
38 t.Errorf("snapshot: count=%d complete=%v err=%v", len(snapshot.Candidates), snapshot.Complete, err)
39 }
40 }()
41 }
42 close(start)
43 wg.Wait()
44 if scans := store.DiscoveryScans(); scans != 1 {
45 t.Fatalf("cold shared scans = %d, want 1", scans)
46 }
47 for i := range 2000 {
48 if _, ok := store.Read(fmt.Sprintf("skill-%04d", i%1154)); !ok {
49 t.Fatalf("warm indexed read %d failed", i)
50 }
51 }
52 if scans := store.DiscoveryScans(); scans != 1 {
53 t.Fatalf("warm reads rescanned: %d", scans)
54 }
55 }
56
57 func TestCatalogSnapshotInvalidationPublishesOnlyCompleteReplacement(t *testing.T) {
58 root := t.TempDir()
59 write := func(name string) {
60 path := filepath.Join(root, name, SkillFile)
61 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
62 t.Fatal(err)
63 }
64 if err := os.WriteFile(path, []byte("---\ndescription: test\n---\nbody"), 0o644); err != nil {
65 t.Fatal(err)
66 }
67 }
68 write("one")
69 store := New(Options{HomeDir: t.TempDir(), CustomPaths: []string{root}, DisableBuiltins: true})
70 t.Cleanup(func() { _ = store.Close() })
71 first, err := store.Snapshot(context.Background())
72 if err != nil || len(first.Candidates) != 1 {
73 t.Fatalf("first snapshot = %+v, %v", first, err)
74 }
75 write("two")
76 store.Invalidate("test")
77 second, err := store.Snapshot(context.Background())
78 if err != nil || len(second.Candidates) != 2 || second.Version == first.Version {
79 t.Fatalf("second snapshot = %+v, %v", second, err)
80 }
81 }
82
83 func TestLoadHonorsCancelledTurnBeforeDiscovery(t *testing.T) {
84 store := New(Options{HomeDir: t.TempDir(), CustomPaths: []string{t.TempDir()}, DisableBuiltins: true})
85 ctx, cancel := context.WithCancel(context.Background())
86 cancel()
87 if _, ok := store.Load(ctx, "cancelled"); ok {
88 t.Fatal("cancelled load resolved a skill")
89 }
90 if scans := store.DiscoveryScans(); scans != 0 {
91 t.Fatalf("cancelled load started %d scans, want zero", scans)
92 }
93 }
94
95 func TestWatchDirectoryScanHonorsCancellationBeforeFilesystemWork(t *testing.T) {
96 ctx, cancel := context.WithCancel(context.Background())
97 cancel()
98 if directories, complete := watchDirectoriesContext(ctx, t.TempDir(), 3); complete || directories != nil {
99 t.Fatalf("cancelled watch scan = (%v, %v), want (nil, false)", directories, complete)
100 }
101 }
102
103 func TestCatalogWatcherInvalidatesCreateRenameAndDelete(t *testing.T) {
104 root := t.TempDir()
105 store := New(Options{HomeDir: t.TempDir(), CustomPaths: []string{root}, DisableBuiltins: true, Watch: true})
106 t.Cleanup(func() { _ = store.Close() })
107 first, err := store.Snapshot(context.Background())
108 if err != nil || len(first.Candidates) != 0 {
109 t.Fatalf("initial snapshot = %+v, %v", first, err)
110 }
111
112 write := func(name string) string {
113 file := filepath.Join(root, name, SkillFile)
114 if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
115 t.Fatal(err)
116 }
117 if err := os.WriteFile(file, []byte("---\ndescription: watched\n---\nbody"), 0o644); err != nil {
118 t.Fatal(err)
119 }
120 return file
121 }
122 write("one")
123 waitCatalogCount(t, store, 1)
124 if err := os.Rename(filepath.Join(root, "one"), filepath.Join(root, "two")); err != nil {
125 t.Fatal(err)
126 }
127 waitCatalogName(t, store, "two")
128 if err := os.RemoveAll(filepath.Join(root, "two")); err != nil {
129 t.Fatal(err)
130 }
131 waitCatalogCount(t, store, 0)
132 }
133
134 func waitCatalogCount(t *testing.T, store *Store, want int) CatalogSnapshot {
135 t.Helper()
136 deadline := time.Now().Add(5 * time.Second)
137 for time.Now().Before(deadline) {
138 snapshot, err := store.Snapshot(context.Background())
139 if err == nil && len(snapshot.Candidates) == want {
140 return snapshot
141 }
142 runtime.Gosched()
143 }
144 t.Fatalf("catalog did not reach count %d", want)
145 return CatalogSnapshot{}
146 }
147
148 func waitCatalogName(t *testing.T, store *Store, want string) {
149 t.Helper()
150 deadline := time.Now().Add(5 * time.Second)
151 for time.Now().Before(deadline) {
152 snapshot, err := store.Snapshot(context.Background())
153 if err == nil && len(snapshot.Candidates) == 1 && snapshot.Candidates[0].Name == want {
154 return
155 }
156 runtime.Gosched()
157 }
158 t.Fatalf("catalog did not resolve %q", want)
159 }
160
160 lines GO