watcher_performance_test.go

  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}