Files
filepuff-mcp/internal/parser/cache_test.go
T

143 lines
3.8 KiB
Go

package parser
import (
"context"
"fmt"
"testing"
"github.com/cespare/xxhash/v2"
)
// TestLRUCacheEviction tests that the LRU cache properly evicts old entries.
func TestLRUCacheEviction(t *testing.T) {
registry := NewRegistry()
ctx := context.Background()
// Create 101 unique Go files (cache size is 100)
for i := 0; i < 101; i++ {
content := []byte(fmt.Sprintf("package main\n\nfunc test%d() {}\n", i))
filename := "test.go"
_, err := registry.Parse(ctx, filename, content)
if err != nil {
t.Fatalf("Parse failed for iteration %d: %v", i, err)
}
}
// The LRU cache should have evicted the oldest entry
// Verify cache size is capped at 100
cacheLen := registry.cache.Len()
if cacheLen > 100 {
t.Errorf("Cache size %d exceeds max size 100", cacheLen)
}
}
// TestCacheHit tests that repeated parsing of the same content uses cache.
func TestCacheHit(t *testing.T) {
registry := NewRegistry()
ctx := context.Background()
content := []byte("package main\n\nfunc test() {}\n")
filename := "test.go"
// First parse
result1, err := registry.Parse(ctx, filename, content)
if err != nil {
t.Fatalf("First parse failed: %v", err)
}
// Second parse should use cache
result2, err := registry.Parse(ctx, filename, content)
if err != nil {
t.Fatalf("Second parse failed: %v", err)
}
// The tree should be the same object (cached)
if result1.Tree != result2.Tree {
t.Error("Expected cached tree to be reused, but got different tree objects")
}
}
// TestContentHashCollisionResistance tests that different content produces different hashes.
func TestContentHashCollisionResistance(t *testing.T) {
testCases := []struct {
name string
content1 []byte
content2 []byte
}{
{
name: "different content",
content1: []byte("package main"),
content2: []byte("package test"),
},
{
name: "same prefix different suffix",
content1: []byte("package main\nfunc a() {}"),
content2: []byte("package main\nfunc b() {}"),
},
{
name: "different length",
content1: []byte("short"),
content2: []byte("much longer content here"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
hash1 := fmt.Sprintf("%016x", xxhash.Sum64(tc.content1))
hash2 := fmt.Sprintf("%016x", xxhash.Sum64(tc.content2))
if hash1 == hash2 {
t.Errorf("Hash collision: %s == %s for different content", hash1, hash2)
}
})
}
}
// TestContentHashConsistency tests that the same content always produces the same hash.
func TestContentHashConsistency(t *testing.T) {
content := []byte("package main\n\nfunc test() {}\n")
hash1 := fmt.Sprintf("%016x", xxhash.Sum64(content))
hash2 := fmt.Sprintf("%016x", xxhash.Sum64(content))
hash3 := fmt.Sprintf("%016x", xxhash.Sum64(content))
if hash1 != hash2 || hash2 != hash3 {
t.Errorf("Hash inconsistency: %s, %s, %s", hash1, hash2, hash3)
}
}
// BenchmarkContentHash_xxHash benchmarks the xxHash implementation.
func BenchmarkContentHash_xxHash(b *testing.B) {
// Typical file content size (10KB)
content := make([]byte, 10*1024)
for i := range content {
content[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%016x", xxhash.Sum64(content))
}
}
// BenchmarkCacheHitRate benchmarks cache performance with realistic workload.
func BenchmarkCacheHitRate(b *testing.B) {
registry := NewRegistry()
ctx := context.Background()
// Create a set of common files that get parsed repeatedly
files := [][]byte{
[]byte("package main\n\nfunc main() {}\n"),
[]byte("package test\n\nimport \"testing\"\n"),
[]byte("package util\n\nfunc helper() string { return \"\" }\n"),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Simulate realistic access pattern with cache hits
content := files[i%len(files)]
_, _ = registry.Parse(ctx, "test.go", content)
}
}