-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathleak_test.go
More file actions
146 lines (115 loc) · 4.11 KB
/
leak_test.go
File metadata and controls
146 lines (115 loc) · 4.11 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package main
import (
"context"
"io"
"log/slog"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestMemoryLeak simulates extended usage to detect memory leaks.
// It performs many scan cycles and checks that memory doesn't grow unboundedly.
func TestMemoryLeak(t *testing.T) {
// Silence logs during test
slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
err := SetupServer("")
require.NoError(t, err)
// Content samples of varying sizes
smallContent := `package main
const key = "AKIATESTKEYEXAMPLE7A"
func main() {}
`
mediumContent := smallContent + generateCode(100)
largeContent := smallContent + generateCode(1000)
contents := []string{smallContent, mediumContent, largeContent}
// Force GC and get baseline memory
runtime.GC()
var baseline runtime.MemStats
runtime.ReadMemStats(&baseline)
ctx := context.Background()
iterations := 1000
// Simulate many document open/scan/close cycles
for i := 0; i < iterations; i++ {
content := contents[i%len(contents)]
uri := "file:///test/file.go"
// Scan (simulates didOpen/didChange)
findings, err := globalServer.scanner.ScanContent(ctx, uri, content)
require.NoError(t, err)
// Store in cache (simulates normal operation)
globalServer.cache.Put(content, findings)
// Every 100 iterations, also test cache retrieval
if i%100 == 0 {
globalServer.cache.Get(content)
}
// Every 200 iterations, clear cache (simulates config reload)
if i%200 == 0 {
globalServer.cache.Clear()
}
}
// Force GC and measure final memory
runtime.GC()
var final runtime.MemStats
runtime.ReadMemStats(&final)
// Calculate memory growth
heapGrowth := int64(final.HeapAlloc) - int64(baseline.HeapAlloc)
heapGrowthMB := float64(heapGrowth) / 1024 / 1024
t.Logf("Memory stats after %d iterations:", iterations)
t.Logf(" Baseline heap: %.2f MB", float64(baseline.HeapAlloc)/1024/1024)
t.Logf(" Final heap: %.2f MB", float64(final.HeapAlloc)/1024/1024)
t.Logf(" Heap growth: %.2f MB", heapGrowthMB)
t.Logf(" Mallocs: %d", final.Mallocs-baseline.Mallocs)
t.Logf(" Frees: %d", final.Frees-baseline.Frees)
// Memory should not grow more than 50MB for this test
// (generous limit to account for GC timing variations)
assert.Less(t, heapGrowthMB, 50.0, "Memory grew too much, possible leak")
// Most allocations should be freed
mallocDiff := final.Mallocs - baseline.Mallocs
freeDiff := final.Frees - baseline.Frees
freeRatio := float64(freeDiff) / float64(mallocDiff)
t.Logf(" Free ratio: %.2f%%", freeRatio*100)
// At least 90% of allocations should be freed after GC
assert.Greater(t, freeRatio, 0.90, "Too many allocations not freed")
}
// TestMemoryLeakWorkspaceScan tests workspace scanning doesn't leak
func TestMemoryLeakWorkspaceScan(t *testing.T) {
slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
err := SetupServer("")
require.NoError(t, err)
// Create temp workspace with 50 files
tmpDir := t.TempDir()
for i := 0; i < 50; i++ {
content := "package test\nfunc example() {}\n"
filename := filepath.Join(tmpDir, "file"+string(rune('a'+i%26))+".go")
require.NoError(t, os.WriteFile(filename, []byte(content), 0644))
}
runtime.GC()
var baseline runtime.MemStats
runtime.ReadMemStats(&baseline)
ctx := context.Background()
// Run workspace scan multiple times
for i := 0; i < 10; i++ {
result, err := globalServer.ScanWorkspace(ctx, tmpDir, nil)
require.NoError(t, err)
require.NotNil(t, result)
// Clear between scans
globalServer.cache.Clear()
}
runtime.GC()
var final runtime.MemStats
runtime.ReadMemStats(&final)
heapGrowthMB := float64(int64(final.HeapAlloc)-int64(baseline.HeapAlloc)) / 1024 / 1024
t.Logf("Workspace scan memory (10 scans of 50 files):")
t.Logf(" Heap growth: %.2f MB", heapGrowthMB)
assert.Less(t, heapGrowthMB, 20.0, "Workspace scan memory grew too much")
}
// generateCode creates filler code of approximately n lines
func generateCode(lines int) string {
var result string
for i := 0; i < lines; i++ {
result += "// This is a comment line for padding\n"
}
return result
}