Created
July 25, 2023 17:44
-
-
Save likern/b9d2abdc017eebde71c4f4acf4b04a1d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const assert = std.debug.assert; | |
const expect = std.testing.expect; | |
const expectEqual = std.testing.expectEqual; | |
const expectError = std.testing.expectError; | |
const expectEqualSlices = std.testing.expectEqualSlices; | |
const MemoryRuntime = @import("memory").MemoryRuntime; | |
const FileBlockManager = @import("io").FileBlockManager; | |
const BlockId = @import("disk").BlockId; | |
const BlockSizeKind = @import("disk").BlockSizeKind; | |
const DataBlock = @import("disk").DataBlock; | |
const DataKind = @import("disk").DataKind; | |
const Block = DataBlock(u8, BlockSizeKind.sixteen_kilobytes); | |
// const OnDiskBlock = [@enumToInt(BlockSizeKind.sixteen_kilobytes)]u8; | |
const OnDiskBlock = [512]u8; | |
const SuperBlock = @import("disk").SuperBlock; | |
const DataBlockHeader = @import("disk").DataBlockHeader; | |
const BlockSerializer = @import("disk").BlockSerializer; | |
const LRUPolicy = @import("pool").LRUPolicy; | |
const BufferPool = @import("pool").BufferPool; | |
const Item = @import("pool").Item; | |
const UnpinItemStatus = @import("pool").UnpinItemStatus; | |
const CreateItemError = @import("pool").CreateItemError; | |
const FetchItemError = @import("pool").FetchItemError; | |
const mem_align = MemoryRuntime.default().mem_align; | |
const superblock_size = SuperBlock.default().db_superblock_size; | |
const block_size = SuperBlock.default().db_block_size; | |
const K = BlockId; | |
const V = OnDiskBlock; | |
const LRU = LRUPolicy(K); | |
const FBM = FileBlockManager; | |
const BPM = BufferPool(K, V); | |
const Test = struct { | |
lru: *LRU, | |
fbm: *FBM, | |
bpm: *BPM, | |
const Self = @This(); | |
pub fn prepare(path: []const u8, pool_size: u64) !Self { | |
const allocator = std.testing.allocator; | |
var lru = try allocator.create(LRU); | |
lru.* = try LRU.create(std.testing.allocator, pool_size); | |
errdefer lru.delete(); | |
var file_block_manager = try allocator.create(FBM); | |
file_block_manager.* = try FBM.open(std.testing.allocator, path); | |
errdefer file_block_manager.close() catch unreachable; | |
var bpm = try allocator.create(BPM); | |
bpm.* = try BPM.create( | |
std.testing.allocator, | |
pool_size, | |
block_size, | |
file_block_manager, | |
lru, | |
); | |
errdefer bpm.delete(); | |
return Self{ | |
.lru = lru, | |
.fbm = file_block_manager, | |
.bpm = bpm, | |
}; | |
} | |
pub fn teardown(self: *Self, delete_db_file: bool) !void { | |
const allocator = std.testing.allocator; | |
self.bpm.delete(); | |
allocator.destroy(self.bpm); | |
if (delete_db_file) { | |
try self.fbm.__db_file.unlink(); | |
} else { | |
try self.fbm.close(); | |
} | |
allocator.destroy(self.fbm); | |
self.lru.delete(); | |
allocator.destroy(self.lru); | |
} | |
// pub fn fillItemWithValue(item: *align(mem_align) DataBlockHeader, block_size: usize, block: []align(mem_align) u8, value: u8) void { | |
// for (block) |*it| { | |
// it.* = @intCast(u8, value); | |
// } | |
// } | |
pub fn fillItem(item: Item(K, V), value: u8, count: u16) void { | |
const data_block = Block.init(DataKind.fixed_size, item.state.getKey(), item.value); | |
const capacity = data_block.capacity(); | |
assert(count <= capacity); | |
for (0..count) |_| { | |
var array_value = [1]u8{0}; | |
array_value[0] = value; | |
_ = data_block.append(&array_value) catch unreachable; | |
} | |
assert(data_block.count() == count); | |
// const value = @intCast(u8, item.state.getKey()); | |
// fillItemWithValue(item.__block_data, value); | |
} | |
pub fn createAndFillNewItem(self: *Self, value: u8, count: u16) !Item(K, V) { | |
var new_item = try self.bpm.createItem(); | |
fillItem(new_item, value, count); | |
return new_item; | |
} | |
pub fn unpinDirty(self: *Self, item: *Item(K, V)) error{UnpinFailed}!void { | |
const status = self.bpm.unpinItem(item.state.getKey(), true) catch unreachable; | |
if (status != UnpinItemStatus.unpinned) { | |
return error.UnpinFailed; | |
} | |
} | |
}; | |
// In later tests we test internal buffer pool implementation, | |
// make sure these fields are still exists | |
// test "test buffer pool has internal fields" { | |
// try expect(@hasField(BufferPool, "__block_statet")); | |
// } | |
// In this test we check properties of | |
// .createBlock(), .unpinBlock(), .fetchBlock() methods. | |
// test "check properties of created, unpinned, fetched blocks" { | |
// const buffer_pool_size = 10; | |
// var t = try Test.prepare("test_create_block.zdb", buffer_pool_size); | |
// defer t.teardown(true) catch unreachable; | |
// // Scenario: | |
// // 1. Created block is pinned by default | |
// // 2. Created block's pin count equal to 1 | |
// var item1 = try t.bpm.createItem(); | |
// try expectEqual(true, item1.state.isPinned()); | |
// try expectEqual(@as(u64, 1), item1.state.getPinCount()); | |
// // Scenario: | |
// // 1. Created block used available free slot in buffer pool | |
// // 2. Created block was not added to target victim policy | |
// try expectEqual(@as(u64, 9), t.bpm.__free_buffer_indexes.length()); | |
// try expectEqual(@as(u64, 0), t.bpm.__policy.length()); | |
// // Scenario: | |
// // 1. Unpinned block still occupies slot in buffer pool | |
// // 2. Unpinned block is added to target victim policy | |
// const unpin_item_status = try t.bpm.unpinItem(item1.state.getKey(), true); | |
// try expectEqual(unpin_item_status, UnpinItemStatus.unpinned); | |
// try expectEqual(@as(u64, 9), t.bpm.__free_buffer_indexes.length()); | |
// try expectEqual(@as(u64, 1), t.bpm.__policy.length()); | |
// // Scenario: | |
// // 1. Fetched block is pinned by default | |
// // 2. Fetching block deletes it from target victim policy | |
// // 3. Fetching block again increases pin count by 1 | |
// item1 = try t.bpm.fetchItem(item1.state.getKey()); | |
// try expectEqual(true, item1.state.isPinned()); | |
// try expectEqual(@as(u64, 1), item1.state.getPinCount()); | |
// try expectEqual(@as(usize, 9), t.bpm.__free_buffer_indexes.length()); | |
// try expectEqual(@as(u64, 0), t.bpm.__policy.length()); | |
// item1 = try t.bpm.fetchItem(item1.state.getKey()); | |
// try expectEqual(true, item1.state.isPinned()); | |
// try expectEqual(@as(u64, 2), item1.state.getPinCount()); | |
// // Scenario: | |
// // 1. Unpinning block decreases pin count by 1 | |
// // 2. Unpinning block again makes them unpinned | |
// _ = try t.bpm.unpinItem(item1.state.getKey(), true); | |
// _ = try t.bpm.unpinItem(item1.state.getKey(), true); | |
// item1 = try t.bpm.fetchItem(item1.state.getKey()); | |
// try expectEqual(true, item1.state.isPinned()); | |
// try expectEqual(@as(u64, 1), item1.state.getPinCount()); | |
// } | |
// In this test we check that .unpinBlock() method | |
// flushes dirty blocks to disk | |
test ".unpinBlock(): dirty blocks flushes to disk\n" { | |
var t = try Test.prepare("test_unpin_block.zdb", 1); | |
defer t.teardown(true) catch unreachable; | |
var item1_copy: [512]u8 = undefined; | |
var item2_copy: [512]u8 = undefined; | |
var item1 = try t.createAndFillNewItem(1, 7); | |
//const blk1_id = item1.__block_state.getBlockId(); | |
try expectEqual(item1_copy.len, item1.value.len); | |
@memcpy(&item1_copy, item1.value); | |
// std.mem.copy(u8, &item1_copy, item1.__block_data); | |
// Once buffer pool is full, we should get error on creating new block | |
const err = t.bpm.createItem(); | |
try expectError(CreateItemError.BufferPoolIsFull, err); | |
// Scenario: unpinned dirty block should be flushed to disk | |
try t.unpinDirty(&item1); | |
var item2 = try t.createAndFillNewItem(2, 8); | |
//const blk2_id = item2.__block_state.getBlockId(); | |
try expectEqual(item2_copy.len, item2.value.len); | |
@memcpy(&item2_copy, item2.value); | |
// std.mem.copy(u8, &item2_copy, item2.__block_data); | |
try t.unpinDirty(&item2); | |
item1 = try t.bpm.fetchItem(item1.state.getKey()); | |
try expectEqualSlices(u8, &item1_copy, item1.value); | |
try t.unpinDirty(&item1); | |
item2 = try t.bpm.fetchItem(item2.state.getKey()); | |
try expectEqualSlices(u8, &item2_copy, item2.value); | |
} | |
// In this test we check that fetching unpinned block | |
// should remove it from target victim policy | |
// test ".fetchBlock(): removes unpinned block from policy\n" { | |
// const buffer_pool_size = 10; | |
// var t = try Test.prepare("test_fetch_unpinned_block.zdb", buffer_pool_size); | |
// defer t.teardown(true) catch unreachable; | |
// var blk1 = try t.bpm.createBlock(); | |
// const blk1_id = blk1.__block_state.getBlockId(); | |
// // Scenario: | |
// // 1. block 1 should be unpinned and added to target victim policy | |
// const unpin_block_status = try t.bpm.unpinBlock(blk1_id, true); | |
// try expectEqual(unpin_block_status, UnpinItemStatus.unpinned); | |
// // Scenario: | |
// // 1. Fetching block should make it pinned and | |
// // removed from target victim policy | |
// blk1 = try t.bpm.fetchBlock(blk1_id); | |
// // Occupy all free slots, so victim block should be searched | |
// var i: u64 = 2; | |
// while (i < (buffer_pool_size + 1)) : (i += 1) { | |
// _ = try t.bpm.createBlock(); | |
// } | |
// // Target victim policy should be empty and all blocks are pinned | |
// const err = t.bpm.createBlock(); | |
// try expectError(CreateItemError.BufferPoolIsFull, err); | |
// } | |
// In this test we check .fetchBlock() method | |
// under different scenarios: | |
// 1. When all buffer pool blocks are pinned - should fail | |
// 2. When at least one buffer pool block is not pinned - should succeed | |
// test ".fetchBlock(): fetches blocks from disk\n" { | |
// const buffer_pool_size = 10; | |
// var t = try Test.prepare("test_fetch_block.zdb", buffer_pool_size); | |
// defer t.teardown(true) catch unreachable; | |
// // Scenario: buffer pool is empty, should be able to create new block | |
// var blk1 = try t.bpm.createBlock(); | |
// const blk1_id = blk1.__block_state.getBlockId(); | |
// // Scenario: once we have a block, we should be able to read and write block data | |
// const message = [5]u8{ 'H', 'e', 'l', 'l', 'o' }; | |
// std.mem.copy(u8, blk1.__block_data, &message); | |
// var written_data = blk1.__block_data[0..message.len]; | |
// try expectEqual(written_data.len, message.len); | |
// try expectEqualSlices(u8, written_data, &message); | |
// // Scenario: should be able to create new blocks until we fill up the buffer pool | |
// var i: u32 = 2; | |
// while (i < (buffer_pool_size + 1)) : (i += 1) { | |
// _ = try t.bpm.createBlock(); | |
// } | |
// // Scenario: one the buffer pool is full, we should not be able to create new blocks | |
// const create_block_error = t.bpm.createBlock(); | |
// try expectError(CreateItemError.BufferPoolIsFull, create_block_error); | |
// // Scenario: after unpinning blocks {1, 2, 3, 4, 5} and pinning another 4 new blocks, | |
// // there would still be one buffer block left for reading block 1 | |
// i = 1; | |
// while (i <= 5) : (i += 1) { | |
// const status = try t.bpm.unpinBlock(i, true); | |
// try expectEqual(status, UnpinItemStatus.unpinned); | |
// } | |
// i = 1; | |
// while (i <= 4) : (i += 1) { | |
// _ = try t.bpm.createBlock(); | |
// } | |
// blk1 = try t.bpm.fetchBlock(blk1_id); | |
// written_data = blk1.__block_data[0..message.len]; | |
// try expectEqual(written_data.len, message.len); | |
// try expectEqualSlices(u8, written_data, &message); | |
// // Scenario: if we unpin block 1 and then make a new block, | |
// // all buffer pool blocks should now be pinned. | |
// // Fetching block 1 should fail. | |
// try t.unpinDirty(&blk1); | |
// _ = try t.bpm.createBlock(); | |
// const fetch_block_error = t.bpm.fetchBlock(blk1_id); | |
// try expectError(FetchItemError.AllBlocksPinned, fetch_block_error); | |
// } | |
// In this test we check properties of | |
// .createBlock(), .unpinBlock(), .fetchBlock() methods. | |
// test ".deleteBlock(): check properties of created, unpinned, fetched blocks\n" { | |
// const buffer_pool_size = 10; | |
// var t = try Test.prepare("test_delete_block.zdb", buffer_pool_size); | |
// defer t.teardown(true) catch unreachable; | |
// // Scenario: | |
// // 1. Deleting block on empty buffer pool fails | |
// // Scenario: | |
// // 1. Created block is pinned by default | |
// // 2. Created block's pin count equal to 1 | |
// var blk1 = try t.bpm.createBlock(); | |
// const blk1_id = blk1.__block_state.getBlockId(); | |
// try expectEqual(true, blk1.__block_state.isPinned()); | |
// try expectEqual(@as(u64, 1), blk1.__block_state.getPinCount()); | |
// // Scenario: | |
// // 1. Created block used available free slot in buffer pool | |
// // 2. Created block was not added to target victim policy | |
// try expectEqual(@as(u64, 9), t.bpm.__free_buffer_indexes.length()); | |
// try expectEqual(@as(u64, 0), t.bpm.__policy.length()); | |
// // Scenario: | |
// // 1. Unpinned block still occupies slot in buffer pool | |
// // 2. Unpinned block is added to target victim policy | |
// const unpin_block_status = try t.bpm.unpinBlock(blk1_id, true); | |
// try expectEqual(unpin_block_status, UnpinItemStatus.unpinned); | |
// try expectEqual(@as(u64, 9), t.bpm.__free_buffer_indexes.length()); | |
// try expectEqual(@as(u64, 1), t.bpm.__policy.length()); | |
// // Scenario: | |
// // 1. Fetched block is pinned by default | |
// // 2. Fetching block deletes it from target victim policy | |
// // 3. Fetching block again increases pin count by 1 | |
// blk1 = try t.bpm.fetchBlock(blk1_id); | |
// try expectEqual(true, blk1.__block_state.isPinned()); | |
// try expectEqual(@as(u64, 1), blk1.__block_state.getPinCount()); | |
// try expectEqual(@as(usize, 9), t.bpm.__free_buffer_indexes.length()); | |
// try expectEqual(@as(u64, 0), t.bpm.__policy.length()); | |
// blk1 = try t.bpm.fetchBlock(blk1_id); | |
// try expectEqual(true, blk1.__block_state.isPinned()); | |
// try expectEqual(@as(u64, 2), blk1.__block_state.getPinCount()); | |
// // Scenario: | |
// // 1. Unpinning block decreases pin count by 1 | |
// // 2. Unpinning block again makes them unpinned | |
// _ = try t.bpm.unpinBlock(blk1_id, true); | |
// _ = try t.bpm.unpinBlock(blk1_id, true); | |
// blk1 = try t.bpm.fetchBlock(blk1_id); | |
// try expectEqual(true, blk1.__block_state.isPinned()); | |
// try expectEqual(@as(u64, 1), blk1.__block_state.getPinCount()); | |
// } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment