Files
pipelinedb/cache_test.go
2025-09-30 15:05:56 +08:00

542 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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++
}
})
}