542 lines
12 KiB
Go
542 lines
12 KiB
Go
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++
|
||
}
|
||
})
|
||
}
|