Files
pipelinedb/cache_test.go

542 lines
12 KiB
Go
Raw Permalink Normal View History

2025-09-30 15:05:56 +08:00
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++
}
})
}