710 lines
16 KiB
Go
710 lines
16 KiB
Go
package pipelinedb
|
||
|
||
import (
|
||
"bytes"
|
||
"errors"
|
||
"os"
|
||
"sync"
|
||
"testing"
|
||
)
|
||
|
||
// TestPageBasicOperations 测试页面基本操作
|
||
func TestPageBasicOperations(t *testing.T) {
|
||
p := make(Page, PageSize)
|
||
|
||
// 测试槽数量操作
|
||
p.setNumSlots(5)
|
||
if p.numSlots() != 5 {
|
||
t.Errorf("numSlots() = %d, want 5", p.numSlots())
|
||
}
|
||
|
||
// 测试空闲偏移操作
|
||
p.setFreeOff(1000)
|
||
if p.freeOff() != 1000 {
|
||
t.Errorf("freeOff() = %d, want 1000", p.freeOff())
|
||
}
|
||
|
||
// 测试下一页操作
|
||
p.setNextPage(42)
|
||
if p.nextPage() != 42 {
|
||
t.Errorf("nextPage() = %d, want 42", p.nextPage())
|
||
}
|
||
|
||
// 测试槽位操作
|
||
p.setSlot(0, 100)
|
||
p.setSlot(1, 200)
|
||
p.setSlot(2, 300)
|
||
|
||
slots := p.slotArray()
|
||
expected := []uint16{100, 200, 300, 0, 0}
|
||
|
||
for i, exp := range expected {
|
||
if slots[i] != exp {
|
||
t.Errorf("slot[%d] = %d, want %d", i, slots[i], exp)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MockFileForStorage 用于测试存储的模拟文件
|
||
type MockFileForStorage struct {
|
||
data []byte
|
||
offset int64
|
||
mu sync.Mutex
|
||
}
|
||
|
||
func NewMockFileForStorage(size int) *MockFileForStorage {
|
||
return &MockFileForStorage{
|
||
data: make([]byte, size),
|
||
}
|
||
}
|
||
|
||
func (f *MockFileForStorage) ReadAt(p []byte, off int64) (n int, err error) {
|
||
f.mu.Lock()
|
||
defer f.mu.Unlock()
|
||
|
||
if off >= int64(len(f.data)) {
|
||
return 0, errors.New("EOF")
|
||
}
|
||
|
||
n = copy(p, f.data[off:])
|
||
return n, nil
|
||
}
|
||
|
||
func (f *MockFileForStorage) WriteAt(p []byte, off int64) (n int, err error) {
|
||
f.mu.Lock()
|
||
defer f.mu.Unlock()
|
||
|
||
// 扩展数据如果需要
|
||
needed := int(off) + len(p)
|
||
if needed > len(f.data) {
|
||
newData := make([]byte, needed)
|
||
copy(newData, f.data)
|
||
f.data = newData
|
||
}
|
||
|
||
n = copy(f.data[off:], p)
|
||
return n, nil
|
||
}
|
||
|
||
func (f *MockFileForStorage) Close() error {
|
||
return nil
|
||
}
|
||
|
||
func (f *MockFileForStorage) Sync() error {
|
||
return nil
|
||
}
|
||
|
||
// TestPipelineDBReadWritePage 测试页面读写操作
|
||
func TestPipelineDBReadWritePage(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_page_rw_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配一个新页面
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
// 创建测试页面数据
|
||
testPage := make(Page, PageSize)
|
||
testPage.setNumSlots(2)
|
||
testPage.setFreeOff(PageSize - 100)
|
||
testPage.setNextPage(123)
|
||
|
||
// 写入页面
|
||
err = pdb.writePage(pageNo, testPage)
|
||
if err != nil {
|
||
t.Fatalf("writePage failed: %v", err)
|
||
}
|
||
|
||
// 读取页面
|
||
readPage, err := pdb.readPage(pageNo)
|
||
if err != nil {
|
||
t.Fatalf("readPage failed: %v", err)
|
||
}
|
||
|
||
// 验证页面内容
|
||
if readPage.numSlots() != 2 {
|
||
t.Errorf("numSlots = %d, want 2", readPage.numSlots())
|
||
}
|
||
if readPage.freeOff() != PageSize-100 {
|
||
t.Errorf("freeOff = %d, want %d", readPage.freeOff(), PageSize-100)
|
||
}
|
||
if readPage.nextPage() != 123 {
|
||
t.Errorf("nextPage = %d, want 123", readPage.nextPage())
|
||
}
|
||
}
|
||
|
||
// TestPipelineDBAllocFreePage 测试页面分配和释放
|
||
func TestPipelineDBAllocFreePage(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_alloc_free_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配多个页面
|
||
var allocatedPages []uint16
|
||
for i := 0; i < 5; i++ {
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage[%d] failed: %v", i, err)
|
||
}
|
||
allocatedPages = append(allocatedPages, pageNo)
|
||
}
|
||
|
||
// 验证页面号是连续的(新数据库)
|
||
for i, pageNo := range allocatedPages {
|
||
expected := uint16(i + 2) // 页面0是头部,页面1是计数器
|
||
if pageNo != expected {
|
||
t.Errorf("allocated page[%d] = %d, want %d", i, pageNo, expected)
|
||
}
|
||
}
|
||
|
||
// 释放一些页面
|
||
pdb.freePage(allocatedPages[1])
|
||
pdb.freePage(allocatedPages[3])
|
||
|
||
// 再次分配页面,应该重用释放的页面
|
||
pageNo1, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage after free failed: %v", err)
|
||
}
|
||
|
||
pageNo2, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage after free failed: %v", err)
|
||
}
|
||
|
||
// 验证重用了释放的页面(LIFO顺序)
|
||
if pageNo1 != allocatedPages[3] || pageNo2 != allocatedPages[1] {
|
||
t.Errorf("reused pages = [%d, %d], want [%d, %d]",
|
||
pageNo1, pageNo2, allocatedPages[3], allocatedPages[1])
|
||
}
|
||
}
|
||
|
||
// TestInsertToPage 测试页面内记录插入
|
||
func TestInsertToPage(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_insert_page_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配一个页面
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
// 插入多条记录
|
||
testData := []struct {
|
||
id int64
|
||
data []byte
|
||
}{
|
||
{1, []byte("record1")},
|
||
{2, []byte("record2")},
|
||
{3, []byte("record3")},
|
||
}
|
||
|
||
for i, test := range testData {
|
||
slotNo, err := pdb.insertToPage(pageNo, test.id, test.data)
|
||
if err != nil {
|
||
t.Fatalf("insertToPage(%d) failed: %v", test.id, err)
|
||
}
|
||
// 槽号从0开始,第一条记录应该在槽0
|
||
expectedSlot := uint16(i)
|
||
if slotNo != expectedSlot {
|
||
t.Errorf("insertToPage(%d) returned slot %d, want %d", test.id, slotNo, expectedSlot)
|
||
}
|
||
}
|
||
|
||
// 验证页面状态
|
||
page, err := pdb.readPage(pageNo)
|
||
if err != nil {
|
||
t.Fatalf("readPage failed: %v", err)
|
||
}
|
||
|
||
if page.numSlots() != uint16(len(testData)) {
|
||
t.Errorf("numSlots = %d, want %d", page.numSlots(), len(testData))
|
||
}
|
||
}
|
||
|
||
// TestInsertToPageFull 测试页面满时的处理
|
||
func TestInsertToPageFull(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_page_full_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配一个页面
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
// 插入大量记录直到页面满
|
||
mediumData := make([]byte, 400) // 400字节数据
|
||
for i := 0; i < len(mediumData); i++ {
|
||
mediumData[i] = byte(i % 256)
|
||
}
|
||
|
||
var insertCount int
|
||
for i := int64(1); i <= 100; i++ {
|
||
_, err := pdb.insertToPage(pageNo, i, mediumData)
|
||
if err != nil {
|
||
// 页面满了,这是预期的
|
||
t.Logf("Page full after inserting %d records", insertCount)
|
||
|
||
// 验证确实是因为页面满而失败
|
||
if err.Error() != "page full" {
|
||
t.Errorf("expected 'page full' error, got: %v", err)
|
||
}
|
||
|
||
// 测试通过,页面确实满了
|
||
return
|
||
}
|
||
insertCount++
|
||
}
|
||
|
||
// 如果循环结束还没有遇到页面满的情况,说明测试有问题
|
||
t.Errorf("should have encountered page full condition, but inserted %d records", insertCount)
|
||
}
|
||
|
||
// TestReadRecord 测试记录读取
|
||
func TestReadRecord(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_read_record_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配页面并插入记录
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
testID := int64(42)
|
||
testData := []byte("test record data")
|
||
|
||
slotNo, err := pdb.insertToPage(pageNo, testID, testData)
|
||
if err != nil {
|
||
t.Fatalf("insertToPage failed: %v", err)
|
||
}
|
||
|
||
// 读取记录
|
||
readData, err := pdb.readRecord(pageNo, slotNo, testID)
|
||
if err != nil {
|
||
t.Fatalf("readRecord failed: %v", err)
|
||
}
|
||
|
||
// 验证数据
|
||
if string(readData) != string(testData) {
|
||
t.Errorf("readRecord data = %q, want %q", string(readData), string(testData))
|
||
}
|
||
|
||
// 测试读取不存在的记录
|
||
_, err = pdb.readRecord(pageNo, slotNo, testID+1) // 错误的ID
|
||
if err == nil {
|
||
t.Error("readRecord should fail with wrong ID")
|
||
}
|
||
}
|
||
|
||
// TestUpdateInPlace 测试原地更新
|
||
func TestUpdateInPlace(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_update_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配页面并插入记录
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
testID := int64(100)
|
||
originalData := []byte("original data")
|
||
|
||
slotNo, err := pdb.insertToPage(pageNo, testID, originalData)
|
||
if err != nil {
|
||
t.Fatalf("insertToPage failed: %v", err)
|
||
}
|
||
|
||
// 测试相同大小的更新(应该成功)
|
||
newData := []byte("updated data") // 相同长度
|
||
err = pdb.updateInPlace(pageNo, slotNo, testID, newData)
|
||
if err != nil {
|
||
t.Fatalf("updateInPlace with same size failed: %v", err)
|
||
}
|
||
|
||
// 验证更新后的数据
|
||
readData, err := pdb.readRecord(pageNo, slotNo, testID)
|
||
if err != nil {
|
||
t.Fatalf("readRecord after update failed: %v", err)
|
||
}
|
||
|
||
if string(readData) != string(newData) {
|
||
t.Errorf("updated data = %q, want %q", string(readData), string(newData))
|
||
}
|
||
|
||
// 测试更大数据的更新(应该失败)
|
||
largerData := []byte("this is much larger data that should not fit")
|
||
err = pdb.updateInPlace(pageNo, slotNo, testID, largerData)
|
||
if err == nil {
|
||
t.Error("updateInPlace with larger data should fail")
|
||
}
|
||
}
|
||
|
||
// TestDeleteRecord 测试记录删除
|
||
func TestDeleteRecord(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_delete_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配页面并插入记录
|
||
pageNo, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
testID := int64(200)
|
||
testData := []byte("record to delete")
|
||
|
||
slotNo, err := pdb.insertToPage(pageNo, testID, testData)
|
||
if err != nil {
|
||
t.Fatalf("insertToPage failed: %v", err)
|
||
}
|
||
|
||
// 验证记录存在
|
||
_, err = pdb.readRecord(pageNo, slotNo, testID)
|
||
if err != nil {
|
||
t.Fatalf("readRecord before delete failed: %v", err)
|
||
}
|
||
|
||
// 删除记录(传入nil索引,简化测试)
|
||
err = pdb.deleteRecord(pageNo, slotNo, testID, nil)
|
||
if err != nil {
|
||
t.Fatalf("deleteRecord failed: %v", err)
|
||
}
|
||
|
||
// 验证读取已删除的记录会失败
|
||
_, err = pdb.readRecord(pageNo, slotNo, testID)
|
||
if err == nil {
|
||
t.Error("readRecord should fail for deleted record")
|
||
}
|
||
}
|
||
|
||
// TestInsertToChain 测试链式插入
|
||
func TestInsertToChain(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_chain_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
tmpFile.Close()
|
||
|
||
config := &Config{CacheSize: 10}
|
||
pdb, err := Open(Options{
|
||
Filename: tmpFile.Name(),
|
||
Config: config,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Open failed: %v", err)
|
||
}
|
||
defer pdb.Stop()
|
||
|
||
// 分配根页面
|
||
rootPage, err := pdb.allocPage()
|
||
if err != nil {
|
||
t.Fatalf("allocPage failed: %v", err)
|
||
}
|
||
|
||
// 插入大量记录,触发链式插入
|
||
largeData := make([]byte, 500) // 500字节数据
|
||
for i := 0; i < len(largeData); i++ {
|
||
largeData[i] = byte(i % 256)
|
||
}
|
||
|
||
var insertedIDs []int64
|
||
for i := int64(1); i <= 20; i++ {
|
||
pageNo, _, err := pdb.insertToChain(rootPage, i, largeData)
|
||
if err != nil {
|
||
t.Fatalf("insertToChain(%d) failed: %v", i, err)
|
||
}
|
||
if pageNo == 0 {
|
||
t.Errorf("insertToChain(%d) returned invalid page 0", i)
|
||
}
|
||
insertedIDs = append(insertedIDs, i)
|
||
}
|
||
|
||
// 验证至少插入了一些记录
|
||
if len(insertedIDs) == 0 {
|
||
t.Error("should be able to insert at least some records")
|
||
}
|
||
|
||
// 验证可能创建了多个页面(链式结构)
|
||
// 这里我们只验证功能正常,不验证具体的页面数量
|
||
t.Logf("Successfully inserted %d records using chain insertion", len(insertedIDs))
|
||
}
|
||
|
||
// TestHighLevelOperations 测试高级数据库操作
|
||
func TestHighLevelOperations(t *testing.T) {
|
||
// 创建临时文件
|
||
tmpFile, err := os.CreateTemp("", "test_storage_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
defer tmpFile.Close()
|
||
|
||
// 创建数据库实例
|
||
pdb := &PipelineDB{
|
||
file: tmpFile,
|
||
cache: NewPageCache(10),
|
||
freePageMgr: NewFreePageManager(),
|
||
indexMgr: NewIndexManager(),
|
||
rowMutexes: make(map[int64]*sync.RWMutex),
|
||
header: &Header{
|
||
TotalPages: 1,
|
||
RootPage: 1,
|
||
},
|
||
}
|
||
|
||
// 初始化根页面
|
||
p := make(Page, PageSize)
|
||
p.setNumSlots(0)
|
||
p.setFreeOff(PageSize)
|
||
p.setNextPage(0)
|
||
pdb.writePage(1, p)
|
||
|
||
group := "test_group"
|
||
testData := []byte("high level test data")
|
||
|
||
// 测试插入
|
||
err = pdb.insert(group, 300, testData)
|
||
if err != nil {
|
||
t.Errorf("insert failed: %v", err)
|
||
}
|
||
|
||
// 测试获取
|
||
data, err := pdb.get(group, 300)
|
||
if err != nil {
|
||
t.Errorf("get failed: %v", err)
|
||
}
|
||
|
||
if !bytes.Equal(data, testData) {
|
||
t.Errorf("get data = %s, want %s", string(data), string(testData))
|
||
}
|
||
|
||
// 测试更新
|
||
newData := []byte("updated high level data")
|
||
err = pdb.update(group, 300, newData)
|
||
if err != nil {
|
||
t.Errorf("update failed: %v", err)
|
||
}
|
||
|
||
// 验证更新
|
||
data, err = pdb.get(group, 300)
|
||
if err != nil {
|
||
t.Errorf("get after update failed: %v", err)
|
||
}
|
||
|
||
if !bytes.Equal(data, newData) {
|
||
t.Errorf("updated data = %s, want %s", string(data), string(newData))
|
||
}
|
||
|
||
// 测试重复插入
|
||
err = pdb.insert(group, 300, testData)
|
||
if err == nil || err.Error() != "record already exists" {
|
||
t.Errorf("expected 'record already exists' error, got %v", err)
|
||
}
|
||
|
||
// 测试不存在的记录
|
||
_, err = pdb.get(group, 999)
|
||
if err == nil || err.Error() != "record not found" {
|
||
t.Errorf("expected 'record not found' error, got %v", err)
|
||
}
|
||
|
||
// 测试不存在的组
|
||
_, err = pdb.get("non_existent_group", 300)
|
||
if err == nil || err.Error() != "group not found" {
|
||
t.Errorf("expected 'group not found' error, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestRangeQuery 测试范围查询
|
||
func TestRangeQuery(t *testing.T) {
|
||
tmpFile, err := os.CreateTemp("", "test_range_*.db")
|
||
if err != nil {
|
||
t.Fatalf("failed to create temp file: %v", err)
|
||
}
|
||
defer os.Remove(tmpFile.Name())
|
||
defer tmpFile.Close()
|
||
|
||
pdb := &PipelineDB{
|
||
file: tmpFile,
|
||
cache: NewPageCache(10),
|
||
freePageMgr: NewFreePageManager(),
|
||
indexMgr: NewIndexManager(),
|
||
rowMutexes: make(map[int64]*sync.RWMutex),
|
||
header: &Header{
|
||
TotalPages: 1,
|
||
RootPage: 1,
|
||
},
|
||
}
|
||
|
||
// 初始化根页面
|
||
p := make(Page, PageSize)
|
||
p.setNumSlots(0)
|
||
p.setFreeOff(PageSize)
|
||
pdb.writePage(1, p)
|
||
|
||
group := "range_test_group"
|
||
|
||
// 插入测试数据
|
||
testRecords := map[int64]string{
|
||
100: "record_100",
|
||
200: "record_200",
|
||
300: "record_300",
|
||
400: "record_400",
|
||
500: "record_500",
|
||
}
|
||
|
||
for id, data := range testRecords {
|
||
err := pdb.insert(group, id, []byte(data))
|
||
if err != nil {
|
||
t.Errorf("insert failed for ID %d: %v", id, err)
|
||
}
|
||
}
|
||
|
||
// 执行范围查询
|
||
var results []struct {
|
||
id int64
|
||
data string
|
||
}
|
||
|
||
err = pdb.rangeQuery(group, 200, 400, func(id int64, data []byte) error {
|
||
results = append(results, struct {
|
||
id int64
|
||
data string
|
||
}{id, string(data)})
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
t.Errorf("rangeQuery failed: %v", err)
|
||
}
|
||
|
||
// 验证结果
|
||
expectedResults := []struct {
|
||
id int64
|
||
data string
|
||
}{
|
||
{200, "record_200"},
|
||
{300, "record_300"},
|
||
{400, "record_400"},
|
||
}
|
||
|
||
if len(results) != len(expectedResults) {
|
||
t.Errorf("result count = %d, want %d", len(results), len(expectedResults))
|
||
}
|
||
|
||
for i, expected := range expectedResults {
|
||
if i >= len(results) {
|
||
t.Errorf("missing result %d", i)
|
||
continue
|
||
}
|
||
|
||
if results[i].id != expected.id || results[i].data != expected.data {
|
||
t.Errorf("result[%d] = {%d, %s}, want {%d, %s}",
|
||
i, results[i].id, results[i].data, expected.id, expected.data)
|
||
}
|
||
}
|
||
}
|