1package watcher
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7 "time"
8
9 "github.com/bmatcuk/doublestar/v4"
10)
11
12// createTestWorkspace creates a temporary workspace with test files
13func createTestWorkspace(tb testing.TB) string {
14 tmpDir, err := os.MkdirTemp("", "watcher_test")
15 if err != nil {
16 tb.Fatal(err)
17 }
18
19 // Create test files for Go project
20 testFiles := []string{
21 "go.mod",
22 "go.sum",
23 "main.go",
24 "src/lib.go",
25 "src/utils.go",
26 "cmd/app.go",
27 "internal/config.go",
28 "internal/db.go",
29 "pkg/api.go",
30 "pkg/client.go",
31 "test/main_test.go",
32 "test/lib_test.go",
33 "docs/README.md",
34 "scripts/build.sh",
35 "Makefile",
36 }
37
38 for _, file := range testFiles {
39 fullPath := filepath.Join(tmpDir, file)
40 dir := filepath.Dir(fullPath)
41
42 if err := os.MkdirAll(dir, 0755); err != nil {
43 tb.Fatal(err)
44 }
45
46 if err := os.WriteFile(fullPath, []byte("// test content"), 0644); err != nil {
47 tb.Fatal(err)
48 }
49 }
50
51 return tmpDir
52}
53
54// simulateOldApproach simulates the old file opening approach with per-file delays
55func simulateOldApproach(workspacePath string, serverName string) (int, time.Duration) {
56 start := time.Now()
57 filesOpened := 0
58
59 // Define patterns for high-priority files based on server type
60 var patterns []string
61
62 switch serverName {
63 case "gopls":
64 patterns = []string{
65 "**/go.mod",
66 "**/go.sum",
67 "**/main.go",
68 }
69 default:
70 patterns = []string{
71 "**/package.json",
72 "**/Makefile",
73 }
74 }
75
76 // OLD APPROACH: For each pattern, find and open matching files with per-file delays
77 for _, pattern := range patterns {
78 matches, err := doublestar.Glob(os.DirFS(workspacePath), pattern)
79 if err != nil {
80 continue
81 }
82
83 for _, match := range matches {
84 fullPath := filepath.Join(workspacePath, match)
85 info, err := os.Stat(fullPath)
86 if err != nil || info.IsDir() {
87 continue
88 }
89
90 // Simulate file opening (1ms overhead)
91 time.Sleep(1 * time.Millisecond)
92 filesOpened++
93
94 // OLD: Add delay after each file
95 time.Sleep(20 * time.Millisecond)
96
97 // Limit files
98 if filesOpened >= 5 {
99 break
100 }
101 }
102 }
103
104 return filesOpened, time.Since(start)
105}
106
107// simulateNewApproach simulates the new batched file opening approach
108func simulateNewApproach(workspacePath string, serverName string) (int, time.Duration) {
109 start := time.Now()
110 filesOpened := 0
111
112 // Define patterns for high-priority files based on server type
113 var patterns []string
114
115 switch serverName {
116 case "gopls":
117 patterns = []string{
118 "**/go.mod",
119 "**/go.sum",
120 "**/main.go",
121 }
122 default:
123 patterns = []string{
124 "**/package.json",
125 "**/Makefile",
126 }
127 }
128
129 // NEW APPROACH: Collect all files first
130 var filesToOpen []string
131
132 // For each pattern, find matching files
133 for _, pattern := range patterns {
134 matches, err := doublestar.Glob(os.DirFS(workspacePath), pattern)
135 if err != nil {
136 continue
137 }
138
139 for _, match := range matches {
140 fullPath := filepath.Join(workspacePath, match)
141 info, err := os.Stat(fullPath)
142 if err != nil || info.IsDir() {
143 continue
144 }
145
146 filesToOpen = append(filesToOpen, fullPath)
147
148 // Limit the number of files per pattern
149 if len(filesToOpen) >= 5 {
150 break
151 }
152 }
153 }
154
155 // Open files in batches to reduce overhead
156 batchSize := 3
157 for i := 0; i < len(filesToOpen); i += batchSize {
158 end := min(i+batchSize, len(filesToOpen))
159
160 // Open batch of files
161 for j := i; j < end; j++ {
162 // Simulate file opening (1ms overhead)
163 time.Sleep(1 * time.Millisecond)
164 filesOpened++
165 }
166
167 // Only add delay between batches, not individual files
168 if end < len(filesToOpen) {
169 time.Sleep(50 * time.Millisecond)
170 }
171 }
172
173 return filesOpened, time.Since(start)
174}
175
176func BenchmarkOldApproach(b *testing.B) {
177 tmpDir := createTestWorkspace(b)
178 defer os.RemoveAll(tmpDir)
179
180 b.ResetTimer()
181 for i := 0; i < b.N; i++ {
182 simulateOldApproach(tmpDir, "gopls")
183 }
184}
185
186func BenchmarkNewApproach(b *testing.B) {
187 tmpDir := createTestWorkspace(b)
188 defer os.RemoveAll(tmpDir)
189
190 b.ResetTimer()
191 for i := 0; i < b.N; i++ {
192 simulateNewApproach(tmpDir, "gopls")
193 }
194}
195
196func TestPerformanceComparison(t *testing.T) {
197 tmpDir := createTestWorkspace(t)
198 defer os.RemoveAll(tmpDir)
199
200 // Test old approach
201 filesOpenedOld, oldDuration := simulateOldApproach(tmpDir, "gopls")
202
203 // Test new approach
204 filesOpenedNew, newDuration := simulateNewApproach(tmpDir, "gopls")
205
206 t.Logf("Old approach: %d files in %v", filesOpenedOld, oldDuration)
207 t.Logf("New approach: %d files in %v", filesOpenedNew, newDuration)
208
209 if newDuration > 0 && oldDuration > 0 {
210 improvement := float64(oldDuration-newDuration) / float64(oldDuration) * 100
211 t.Logf("Performance improvement: %.1f%%", improvement)
212
213 if improvement <= 0 {
214 t.Errorf("Expected performance improvement, but new approach was slower")
215 }
216 }
217
218 // Verify same number of files opened
219 if filesOpenedOld != filesOpenedNew {
220 t.Errorf("Different number of files opened: old=%d, new=%d", filesOpenedOld, filesOpenedNew)
221 }
222
223 // Verify new approach is faster
224 if newDuration >= oldDuration {
225 t.Errorf("New approach should be faster: old=%v, new=%v", oldDuration, newDuration)
226 }
227}