Files
pipelinedb/freepage_test.go

580 lines
14 KiB
Go
Raw Permalink Normal View History

2025-09-30 15:05:56 +08:00
package pipelinedb
import (
"encoding/binary"
"errors"
"sync"
"testing"
)
// TestNewFreePageManager 测试空闲页面管理器的创建
func TestNewFreePageManager(t *testing.T) {
fpm := NewFreePageManager()
if fpm == nil {
t.Fatal("NewFreePageManager returned nil")
}
if fpm.FreeCount() != 0 {
t.Errorf("initial free count = %d, want 0", fpm.FreeCount())
}
// 验证初始状态下没有空闲页面
freePages := fpm.GetFreePages()
if len(freePages) != 0 {
t.Errorf("initial free pages length = %d, want 0", len(freePages))
}
}
// TestFreePageManagerAllocPage 测试页面分配
func TestFreePageManagerAllocPage(t *testing.T) {
fpm := NewFreePageManager()
// 测试空管理器分配页面
pageNo, ok := fpm.AllocPage()
if ok {
t.Error("AllocPage should return false when no free pages available")
}
if pageNo != 0 {
t.Errorf("AllocPage returned pageNo = %d, want 0", pageNo)
}
// 添加一些空闲页面
testPages := []uint16{10, 20, 30}
for _, page := range testPages {
fpm.FreePage(page)
}
// 验证页面数量
if fpm.FreeCount() != len(testPages) {
t.Errorf("free count = %d, want %d", fpm.FreeCount(), len(testPages))
}
// 测试LIFO分配后进先出
expectedOrder := []uint16{30, 20, 10} // 反向顺序
for i, expected := range expectedOrder {
pageNo, ok := fpm.AllocPage()
if !ok {
t.Errorf("AllocPage[%d] should return true", i)
}
if pageNo != expected {
t.Errorf("AllocPage[%d] = %d, want %d", i, pageNo, expected)
}
}
// 验证所有页面都被分配完
if fpm.FreeCount() != 0 {
t.Errorf("free count after allocation = %d, want 0", fpm.FreeCount())
}
// 再次尝试分配应该失败
_, ok = fpm.AllocPage()
if ok {
t.Error("AllocPage should return false when all pages allocated")
}
}
// TestFreePageManagerFreePage 测试页面释放
func TestFreePageManagerFreePage(t *testing.T) {
fpm := NewFreePageManager()
// 释放一个页面
fpm.FreePage(100)
if fpm.FreeCount() != 1 {
t.Errorf("free count after FreePage = %d, want 1", fpm.FreeCount())
}
// 验证页面可以被分配
pageNo, ok := fpm.AllocPage()
if !ok || pageNo != 100 {
t.Errorf("AllocPage = (%d, %t), want (100, true)", pageNo, ok)
}
// 测试重复释放同一页面(应该是幂等操作)
fpm.FreePage(200)
fpm.FreePage(200) // 重复释放
if fpm.FreeCount() != 1 {
t.Errorf("free count after duplicate FreePage = %d, want 1", fpm.FreeCount())
}
// 验证只有一个页面200
pageNo, ok = fpm.AllocPage()
if !ok || pageNo != 200 {
t.Errorf("AllocPage after duplicate free = (%d, %t), want (200, true)", pageNo, ok)
}
// 验证没有更多页面
_, ok = fpm.AllocPage()
if ok {
t.Error("should have no more pages after allocating the only one")
}
}
// TestFreePageManagerGetFreePages 测试获取空闲页面列表
func TestFreePageManagerGetFreePages(t *testing.T) {
fpm := NewFreePageManager()
// 添加一些页面
testPages := []uint16{5, 15, 25, 35}
for _, page := range testPages {
fpm.FreePage(page)
}
// 获取空闲页面列表
freePages := fpm.GetFreePages()
if len(freePages) != len(testPages) {
t.Errorf("free pages length = %d, want %d", len(freePages), len(testPages))
}
// 验证返回的是副本(修改不影响原始数据)
originalCount := fpm.FreeCount()
freePages[0] = 999 // 修改副本
if fpm.FreeCount() != originalCount {
t.Error("modifying returned slice affected original data")
}
// 验证原始数据未被修改
newFreePages := fpm.GetFreePages()
if newFreePages[0] == 999 {
t.Error("original data was modified when returned slice was changed")
}
}
// TestFreePageManagerLoadFromHeader 测试从文件头加载空闲页面
func TestFreePageManagerLoadFromHeader(t *testing.T) {
fpm := NewFreePageManager()
// 模拟页面数据
const testPageSize = 4096
pages := make(map[uint16][]byte)
// 创建空闲页面链表1 -> 2 -> 3 -> 0
// 页面1
page1 := make([]byte, testPageSize)
binary.LittleEndian.PutUint16(page1[4:6], 2) // 下一页是2
pages[1] = page1
// 页面2
page2 := make([]byte, testPageSize)
binary.LittleEndian.PutUint16(page2[4:6], 3) // 下一页是3
pages[2] = page2
// 页面3
page3 := make([]byte, testPageSize)
binary.LittleEndian.PutUint16(page3[4:6], 0) // 链表结束
pages[3] = page3
// 模拟读取函数
readPageFunc := func(pageNo uint16) ([]byte, error) {
if page, exists := pages[pageNo]; exists {
return page, nil
}
return nil, errors.New("page not found")
}
// 从头页面1开始加载
err := fpm.LoadFromHeader(1, readPageFunc)
if err != nil {
t.Errorf("LoadFromHeader returned error: %v", err)
}
// 验证加载的页面数量
if fpm.FreeCount() != 3 {
t.Errorf("free count after load = %d, want 3", fpm.FreeCount())
}
// 验证页面顺序(应该按链表顺序加载)
expectedPages := []uint16{1, 2, 3}
freePages := fpm.GetFreePages()
for i, expected := range expectedPages {
if freePages[i] != expected {
t.Errorf("loaded page[%d] = %d, want %d", i, freePages[i], expected)
}
}
}
// TestFreePageManagerLoadFromHeaderEmpty 测试加载空链表
func TestFreePageManagerLoadFromHeaderEmpty(t *testing.T) {
fpm := NewFreePageManager()
// 先添加一些页面
fpm.FreePage(100)
fpm.FreePage(200)
// 模拟读取函数(不会被调用)
readPageFunc := func(pageNo uint16) ([]byte, error) {
t.Error("readPageFunc should not be called for empty list")
return nil, errors.New("unexpected call")
}
// 从空链表加载头页面为0
err := fpm.LoadFromHeader(0, readPageFunc)
if err != nil {
t.Errorf("LoadFromHeader with empty list returned error: %v", err)
}
// 验证原有页面被清空
if fpm.FreeCount() != 0 {
t.Errorf("free count after loading empty list = %d, want 0", fpm.FreeCount())
}
}
// TestFreePageManagerLoadFromHeaderError 测试加载过程中的错误处理
func TestFreePageManagerLoadFromHeaderError(t *testing.T) {
fpm := NewFreePageManager()
// 模拟读取函数,第二次调用时返回错误
callCount := 0
readPageFunc := func(pageNo uint16) ([]byte, error) {
callCount++
if callCount == 2 {
return nil, errors.New("simulated read error")
}
// 第一次调用返回指向页面2的数据
page := make([]byte, 4096)
binary.LittleEndian.PutUint16(page[4:6], 2)
return page, nil
}
// 尝试加载,应该在第二次读取时失败
err := fpm.LoadFromHeader(1, readPageFunc)
if err == nil {
t.Error("LoadFromHeader should return error when read fails")
}
if err.Error() != "simulated read error" {
t.Errorf("error message = %s, want 'simulated read error'", err.Error())
}
}
// TestFreePageManagerSaveToHeader 测试保存空闲页面到文件头
func TestFreePageManagerSaveToHeader(t *testing.T) {
fpm := NewFreePageManager()
// 添加一些空闲页面
testPages := []uint16{10, 20, 30}
for _, page := range testPages {
fpm.FreePage(page)
}
// 用于存储写入的页面数据
writtenPages := make(map[uint16][]byte)
// 模拟写入函数
writePageFunc := func(pageNo uint16, data []byte) error {
writtenPages[pageNo] = make([]byte, len(data))
copy(writtenPages[pageNo], data)
return nil
}
// 保存到文件头
headPage, err := fpm.SaveToHeader(writePageFunc)
if err != nil {
t.Errorf("SaveToHeader returned error: %v", err)
}
// 验证返回的头页面
if headPage != testPages[0] {
t.Errorf("head page = %d, want %d", headPage, testPages[0])
}
// 验证写入的页面数量
if len(writtenPages) != len(testPages) {
t.Errorf("written pages count = %d, want %d", len(writtenPages), len(testPages))
}
// 验证链表结构
for i, pageNo := range testPages {
data, exists := writtenPages[pageNo]
if !exists {
t.Errorf("page %d was not written", pageNo)
continue
}
// 检查下一页指针
nextPage := binary.LittleEndian.Uint16(data[4:6])
if i < len(testPages)-1 {
// 不是最后一页,应该指向下一页
expectedNext := testPages[i+1]
if nextPage != expectedNext {
t.Errorf("page %d next pointer = %d, want %d", pageNo, nextPage, expectedNext)
}
} else {
// 最后一页应该指向0
if nextPage != 0 {
t.Errorf("last page %d next pointer = %d, want 0", pageNo, nextPage)
}
}
}
}
// TestFreePageManagerSaveToHeaderEmpty 测试保存空列表
func TestFreePageManagerSaveToHeaderEmpty(t *testing.T) {
fpm := NewFreePageManager()
// 模拟写入函数(不应该被调用)
writePageFunc := func(pageNo uint16, data []byte) error {
t.Error("writePageFunc should not be called for empty list")
return errors.New("unexpected call")
}
// 保存空列表
headPage, err := fpm.SaveToHeader(writePageFunc)
if err != nil {
t.Errorf("SaveToHeader with empty list returned error: %v", err)
}
// 验证返回的头页面为0
if headPage != 0 {
t.Errorf("head page for empty list = %d, want 0", headPage)
}
}
// TestFreePageManagerSaveToHeaderError 测试保存过程中的错误处理
func TestFreePageManagerSaveToHeaderError(t *testing.T) {
fpm := NewFreePageManager()
// 添加一些页面
fpm.FreePage(10)
fpm.FreePage(20)
// 模拟写入函数,第二次调用时返回错误
callCount := 0
writePageFunc := func(pageNo uint16, data []byte) error {
callCount++
if callCount == 2 {
return errors.New("simulated write error")
}
return nil
}
// 尝试保存,应该在第二次写入时失败
headPage, err := fpm.SaveToHeader(writePageFunc)
if err == nil {
t.Error("SaveToHeader should return error when write fails")
}
if err.Error() != "simulated write error" {
t.Errorf("error message = %s, want 'simulated write error'", err.Error())
}
if headPage != 0 {
t.Errorf("head page on error = %d, want 0", headPage)
}
}
// TestFreePageManagerRoundTrip 测试保存和加载的往返操作
func TestFreePageManagerRoundTrip(t *testing.T) {
// 创建第一个管理器并添加页面
fpm1 := NewFreePageManager()
originalPages := []uint16{100, 200, 300, 400}
for _, page := range originalPages {
fpm1.FreePage(page)
}
// 用于存储页面数据的映射
pageStorage := make(map[uint16][]byte)
// 保存到存储
writePageFunc := func(pageNo uint16, data []byte) error {
pageStorage[pageNo] = make([]byte, len(data))
copy(pageStorage[pageNo], data)
return nil
}
headPage, err := fpm1.SaveToHeader(writePageFunc)
if err != nil {
t.Errorf("SaveToHeader failed: %v", err)
}
// 创建第二个管理器并从存储加载
fpm2 := NewFreePageManager()
readPageFunc := func(pageNo uint16) ([]byte, error) {
if data, exists := pageStorage[pageNo]; exists {
return data, nil
}
return nil, errors.New("page not found")
}
err = fpm2.LoadFromHeader(headPage, readPageFunc)
if err != nil {
t.Errorf("LoadFromHeader failed: %v", err)
}
// 验证加载的页面与原始页面相同
if fpm2.FreeCount() != len(originalPages) {
t.Errorf("loaded free count = %d, want %d", fpm2.FreeCount(), len(originalPages))
}
loadedPages := fpm2.GetFreePages()
for i, expected := range originalPages {
if loadedPages[i] != expected {
t.Errorf("loaded page[%d] = %d, want %d", i, loadedPages[i], expected)
}
}
// 验证分配顺序相同LIFO
for i := len(originalPages) - 1; i >= 0; i-- {
expected := originalPages[i]
pageNo, ok := fpm2.AllocPage()
if !ok || pageNo != expected {
t.Errorf("AllocPage[%d] = (%d, %t), want (%d, true)", i, pageNo, ok, expected)
}
}
}
// TestFreePageManagerConcurrency 测试并发安全性
func TestFreePageManagerConcurrency(t *testing.T) {
fpm := NewFreePageManager()
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)
// 释放页面
fpm.FreePage(pageNo)
// 尝试分配页面
if allocPage, ok := fpm.AllocPage(); ok {
// 再次释放分配到的页面
fpm.FreePage(allocPage)
}
}
}(i)
}
// 同时进行统计查询
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 50; i++ {
fpm.FreeCount()
fpm.GetFreePages()
}
}()
// 等待所有goroutine完成
wg.Wait()
// 验证管理器仍然可用
fpm.FreePage(9999)
pageNo, ok := fpm.AllocPage()
if !ok || pageNo != 9999 {
t.Error("manager corrupted after concurrent operations")
}
}
// TestFreePageManagerLargeList 测试大量页面的处理
func TestFreePageManagerLargeList(t *testing.T) {
fpm := NewFreePageManager()
// 添加大量页面
const numPages = 1000
for i := uint16(1); i <= numPages; i++ {
fpm.FreePage(i)
}
// 验证数量
if fpm.FreeCount() != numPages {
t.Errorf("free count = %d, want %d", fpm.FreeCount(), numPages)
}
// 分配所有页面
allocatedPages := make([]uint16, 0, numPages)
for i := 0; i < numPages; i++ {
pageNo, ok := fpm.AllocPage()
if !ok {
t.Errorf("AllocPage[%d] failed", i)
break
}
allocatedPages = append(allocatedPages, pageNo)
}
// 验证分配完毕
if fpm.FreeCount() != 0 {
t.Errorf("free count after allocation = %d, want 0", fpm.FreeCount())
}
// 验证LIFO顺序最后添加的最先分配
for i, pageNo := range allocatedPages {
expected := uint16(numPages - i)
if pageNo != expected {
t.Errorf("allocated page[%d] = %d, want %d", i, pageNo, expected)
}
}
}
// BenchmarkFreePageManagerAllocPage 性能测试:页面分配
func BenchmarkFreePageManagerAllocPage(b *testing.B) {
fpm := NewFreePageManager()
// 预填充大量空闲页面
for i := uint16(0); i < 10000; i++ {
fpm.FreePage(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
fpm.AllocPage()
if fpm.FreeCount() == 0 {
// 重新填充
for j := uint16(0); j < 10000; j++ {
fpm.FreePage(j)
}
}
}
}
// BenchmarkFreePageManagerFreePage 性能测试:页面释放
func BenchmarkFreePageManagerFreePage(b *testing.B) {
fpm := NewFreePageManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
fpm.FreePage(uint16(i % 10000))
}
}
// BenchmarkFreePageManagerConcurrentAccess 性能测试:并发访问
func BenchmarkFreePageManagerConcurrentAccess(b *testing.B) {
fpm := NewFreePageManager()
// 预填充一些页面
for i := uint16(0); i < 1000; i++ {
fpm.FreePage(i)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
if i%2 == 0 {
fpm.AllocPage()
} else {
fpm.FreePage(uint16(i % 1000))
}
i++
}
})
}