Skip to content

Instantly share code, notes, and snippets.

@likern
Created July 25, 2023 17:44
Show Gist options
  • Save likern/b9d2abdc017eebde71c4f4acf4b04a1d to your computer and use it in GitHub Desktop.
Save likern/b9d2abdc017eebde71c4f4acf4b04a1d to your computer and use it in GitHub Desktop.
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