package pipelinedb import ( "sync" "testing" "time" ) // TestNewPageCache 测试页面缓存的创建 func TestNewPageCache(t *testing.T) { tests := []struct { name string capacity int wantSize int }{ { name: "创建小容量缓存", capacity: 10, wantSize: 0, }, { name: "创建中等容量缓存", capacity: 100, wantSize: 0, }, { name: "创建大容量缓存", capacity: 1000, wantSize: 0, }, { name: "创建零容量缓存", capacity: 0, wantSize: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cache := NewPageCache(tt.capacity) if cache == nil { t.Fatal("NewPageCache returned nil") } if cache.capacity != tt.capacity { t.Errorf("capacity = %d, want %d", cache.capacity, tt.capacity) } if cache.Size() != tt.wantSize { t.Errorf("initial size = %d, want %d", cache.Size(), tt.wantSize) } // 验证统计信息初始状态 hits, misses, hitRate := cache.Stats() if hits != 0 || misses != 0 || hitRate != 0.0 { t.Errorf("initial stats: hits=%d, misses=%d, hitRate=%f, want all zero", hits, misses, hitRate) } }) } } // TestPageCachePutGet 测试基本的Put和Get操作 func TestPageCachePutGet(t *testing.T) { cache := NewPageCache(10) // 测试数据 testData := []byte("test page data") pageNo := uint16(1) // 测试Put操作 cache.Put(pageNo, testData, false) // 验证缓存大小 if cache.Size() != 1 { t.Errorf("size after put = %d, want 1", cache.Size()) } // 测试Get操作 data, found := cache.Get(pageNo) if !found { t.Fatal("Get returned false, want true") } if string(data) != string(testData) { t.Errorf("Get returned %s, want %s", string(data), string(testData)) } // 验证统计信息 hits, misses, hitRate := cache.Stats() if hits != 1 || misses != 0 { t.Errorf("stats after get: hits=%d, misses=%d, want hits=1, misses=0", hits, misses) } if hitRate != 1.0 { t.Errorf("hit rate = %f, want 1.0", hitRate) } } // TestPageCacheGetMiss 测试缓存未命中的情况 func TestPageCacheGetMiss(t *testing.T) { cache := NewPageCache(10) // 尝试获取不存在的页面 data, found := cache.Get(999) if found { t.Error("Get returned true for non-existent page, want false") } if data != nil { t.Error("Get returned non-nil data for non-existent page, want nil") } // 验证统计信息 hits, misses, hitRate := cache.Stats() if hits != 0 || misses != 1 { t.Errorf("stats after miss: hits=%d, misses=%d, want hits=0, misses=1", hits, misses) } if hitRate != 0.0 { t.Errorf("hit rate = %f, want 0.0", hitRate) } } // TestPageCacheLRUEviction 测试LRU淘汰机制 func TestPageCacheLRUEviction(t *testing.T) { cache := NewPageCache(3) // 小容量缓存便于测试淘汰 // 添加3个页面,填满缓存 for i := uint16(1); i <= 3; i++ { data := []byte("page " + string(rune('0'+i))) cache.Put(i, data, false) } // 验证缓存已满 if cache.Size() != 3 { t.Errorf("size after filling cache = %d, want 3", cache.Size()) } // 访问页面1,使其成为最近使用的 cache.Get(1) // 添加第4个页面,应该淘汰页面2(最久未使用) cache.Put(4, []byte("page 4"), false) // 验证缓存大小仍为3 if cache.Size() != 3 { t.Errorf("size after eviction = %d, want 3", cache.Size()) } // 验证页面1仍然存在(最近访问过) _, found := cache.Get(1) if !found { t.Error("page 1 was evicted, but it should be kept (recently accessed)") } // 验证页面2被淘汰 _, found = cache.Get(2) if found { t.Error("page 2 should be evicted") } // 验证页面3和4仍然存在 _, found = cache.Get(3) if !found { t.Error("page 3 should still exist") } _, found = cache.Get(4) if !found { t.Error("page 4 should exist (just added)") } } // TestPageCacheDirtyPages 测试脏页管理 func TestPageCacheDirtyPages(t *testing.T) { cache := NewPageCache(10) // 添加一个干净页面 cache.Put(1, []byte("clean page"), false) // 添加一个脏页面 cache.Put(2, []byte("dirty page"), true) // 更新页面1为脏页 cache.Put(1, []byte("updated page"), true) // 验证缓存大小 if cache.Size() != 2 { t.Errorf("size = %d, want 2", cache.Size()) } // 测试Flush操作 flushedPages := make(map[uint16][]byte) flushFunc := func(pageNo uint16, data []byte) error { flushedPages[pageNo] = make([]byte, len(data)) copy(flushedPages[pageNo], data) return nil } err := cache.Flush(flushFunc) if err != nil { t.Errorf("Flush returned error: %v", err) } // 验证脏页被刷新 if len(flushedPages) != 2 { t.Errorf("flushed %d pages, want 2", len(flushedPages)) } if string(flushedPages[1]) != "updated page" { t.Errorf("flushed page 1 = %s, want 'updated page'", string(flushedPages[1])) } if string(flushedPages[2]) != "dirty page" { t.Errorf("flushed page 2 = %s, want 'dirty page'", string(flushedPages[2])) } } // TestPageCacheInvalidate 测试页面失效操作 func TestPageCacheInvalidate(t *testing.T) { cache := NewPageCache(10) // 添加几个页面 cache.Put(1, []byte("page 1"), false) cache.Put(2, []byte("page 2"), true) cache.Put(3, []byte("page 3"), false) // 验证初始状态 if cache.Size() != 3 { t.Errorf("initial size = %d, want 3", cache.Size()) } // 使页面2失效 cache.Invalidate(2) // 验证页面2被移除 if cache.Size() != 2 { t.Errorf("size after invalidate = %d, want 2", cache.Size()) } _, found := cache.Get(2) if found { t.Error("invalidated page 2 should not be found") } // 验证其他页面仍然存在 _, found = cache.Get(1) if !found { t.Error("page 1 should still exist") } _, found = cache.Get(3) if !found { t.Error("page 3 should still exist") } // 测试失效不存在的页面(应该是幂等操作) cache.Invalidate(999) if cache.Size() != 2 { t.Errorf("size after invalidating non-existent page = %d, want 2", cache.Size()) } } // TestPageCacheClear 测试清空缓存操作 func TestPageCacheClear(t *testing.T) { cache := NewPageCache(10) // 添加一些页面 for i := uint16(1); i <= 5; i++ { cache.Put(i, []byte("page"), false) } // 产生一些统计数据 cache.Get(1) cache.Get(999) // miss // 验证初始状态 if cache.Size() != 5 { t.Errorf("initial size = %d, want 5", cache.Size()) } hits, misses, _ := cache.Stats() if hits == 0 && misses == 0 { t.Error("should have some stats before clear") } // 清空缓存 cache.Clear() // 验证缓存被清空 if cache.Size() != 0 { t.Errorf("size after clear = %d, want 0", cache.Size()) } // 验证统计信息被重置 hits, misses, hitRate := cache.Stats() if hits != 0 || misses != 0 || hitRate != 0.0 { t.Errorf("stats after clear: hits=%d, misses=%d, hitRate=%f, want all zero", hits, misses, hitRate) } // 验证页面不再存在 _, found := cache.Get(1) if found { t.Error("page should not exist after clear") } } // TestPageCacheDataIsolation 测试数据隔离(副本机制) func TestPageCacheDataIsolation(t *testing.T) { cache := NewPageCache(10) // 原始数据 originalData := []byte("original data") pageNo := uint16(1) // 存储数据 cache.Put(pageNo, originalData, false) // 修改原始数据 originalData[0] = 'X' // 获取缓存数据 cachedData, found := cache.Get(pageNo) if !found { t.Fatal("page not found in cache") } // 验证缓存数据未被修改 if cachedData[0] == 'X' { t.Error("cached data was modified when original data changed") } if string(cachedData) != "original data" { t.Errorf("cached data = %s, want 'original data'", string(cachedData)) } // 修改获取到的数据 cachedData[0] = 'Y' // 再次获取,验证缓存中的数据未被修改 cachedData2, found := cache.Get(pageNo) if !found { t.Fatal("page not found in cache") } if cachedData2[0] == 'Y' { t.Error("cached data was modified when returned data changed") } if string(cachedData2) != "original data" { t.Errorf("cached data = %s, want 'original data'", string(cachedData2)) } } // TestPageCacheConcurrency 测试并发安全性 func TestPageCacheConcurrency(t *testing.T) { cache := NewPageCache(100) const numGoroutines = 10 const numOperations = 100 var wg sync.WaitGroup // 启动多个goroutine进行并发操作 for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < numOperations; j++ { pageNo := uint16(id*numOperations + j) data := []byte("data from goroutine " + string(rune('0'+id))) // 并发Put操作 cache.Put(pageNo, data, j%2 == 0) // 一半是脏页 // 并发Get操作 cache.Get(pageNo) // 偶尔进行Invalidate操作 if j%10 == 0 { cache.Invalidate(pageNo) } } }(i) } // 同时进行统计查询 wg.Add(1) go func() { defer wg.Done() for i := 0; i < 50; i++ { cache.Stats() cache.Size() time.Sleep(time.Millisecond) } }() // 等待所有goroutine完成 wg.Wait() // 验证缓存仍然可用 cache.Put(9999, []byte("final test"), false) data, found := cache.Get(9999) if !found || string(data) != "final test" { t.Error("cache corrupted after concurrent operations") } } // TestPageCacheStatsAccuracy 测试统计信息的准确性 func TestPageCacheStatsAccuracy(t *testing.T) { cache := NewPageCache(10) // 执行一系列操作 cache.Put(1, []byte("page 1"), false) cache.Put(2, []byte("page 2"), false) // 2次命中 cache.Get(1) cache.Get(2) // 3次未命中 cache.Get(3) cache.Get(4) cache.Get(5) // 1次命中 cache.Get(1) // 验证统计信息 hits, misses, hitRate := cache.Stats() expectedHits := int64(3) expectedMisses := int64(3) expectedHitRate := float64(3) / float64(6) if hits != expectedHits { t.Errorf("hits = %d, want %d", hits, expectedHits) } if misses != expectedMisses { t.Errorf("misses = %d, want %d", misses, expectedMisses) } if hitRate != expectedHitRate { t.Errorf("hit rate = %f, want %f", hitRate, expectedHitRate) } } // TestPageCacheUpdateExisting 测试更新已存在页面 func TestPageCacheUpdateExisting(t *testing.T) { cache := NewPageCache(10) pageNo := uint16(1) // 添加初始页面 cache.Put(pageNo, []byte("original"), false) // 验证初始数据 data, found := cache.Get(pageNo) if !found || string(data) != "original" { t.Errorf("initial data = %s, want 'original'", string(data)) } // 更新页面数据 cache.Put(pageNo, []byte("updated"), true) // 验证缓存大小没有增加 if cache.Size() != 1 { t.Errorf("size after update = %d, want 1", cache.Size()) } // 验证数据已更新 data, found = cache.Get(pageNo) if !found || string(data) != "updated" { t.Errorf("updated data = %s, want 'updated'", string(data)) } // 验证脏页标记 flushedCount := 0 flushFunc := func(pageNo uint16, data []byte) error { flushedCount++ return nil } cache.Flush(flushFunc) if flushedCount != 1 { t.Errorf("flushed %d pages, want 1 (dirty page)", flushedCount) } } // BenchmarkPageCacheGet 性能测试:Get操作 func BenchmarkPageCacheGet(b *testing.B) { cache := NewPageCache(1000) // 预填充缓存 for i := uint16(0); i < 1000; i++ { cache.Put(i, []byte("benchmark data"), false) } b.ResetTimer() for i := 0; i < b.N; i++ { cache.Get(uint16(i % 1000)) } } // BenchmarkPageCachePut 性能测试:Put操作 func BenchmarkPageCachePut(b *testing.B) { cache := NewPageCache(1000) data := []byte("benchmark data") b.ResetTimer() for i := 0; i < b.N; i++ { cache.Put(uint16(i%1000), data, false) } } // BenchmarkPageCacheConcurrentAccess 性能测试:并发访问 func BenchmarkPageCacheConcurrentAccess(b *testing.B) { cache := NewPageCache(1000) data := []byte("benchmark data") // 预填充缓存 for i := uint16(0); i < 1000; i++ { cache.Put(i, data, false) } b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { if i%2 == 0 { cache.Get(uint16(i % 1000)) } else { cache.Put(uint16(i%1000), data, false) } i++ } }) }