test(unit): add bloom, cache, compression, batch, and stats tests
This commit is contained in:
1 parent
6bb904de2c
commit
e2e68230f6
6 files changed
+203
No files matched your search
@@ -44,3 +44,19 @@ pub const WriteBatch = struct {
|
||||
return self.ops.items.len;
|
||||
}
|
||||
};
|
||||
|
||||
test "write batch accumulates typed ops" {
|
||||
const allocator = std.testing.allocator;
|
||||
var batch = WriteBatch.init(allocator);
|
||||
defer batch.deinit();
|
||||
|
||||
try batch.put("alpha", "one");
|
||||
try batch.put("bravo", "two");
|
||||
try batch.delete("charlie");
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 3), batch.count());
|
||||
try std.testing.expectEqual(Kind.value, batch.ops.items[0].kind);
|
||||
try std.testing.expectEqualStrings("bravo", batch.ops.items[1].key);
|
||||
try std.testing.expectEqual(Kind.tombstone, batch.ops.items[2].kind);
|
||||
try std.testing.expectEqual(@as(usize, 0), batch.ops.items[2].value.len);
|
||||
}
|
||||
@@ -76,3 +76,40 @@ pub const Bloom = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "bloom no false negatives" {
|
||||
const allocator = std.testing.allocator;
|
||||
var bloom = try Bloom.init(allocator, 1000, 0.01);
|
||||
defer bloom.deinit();
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < 1000) : (i += 1) {
|
||||
const key = try std.fmt.bufPrint(&buf, "key-{d}", .{i});
|
||||
bloom.add(key);
|
||||
}
|
||||
i = 0;
|
||||
while (i < 1000) : (i += 1) {
|
||||
const key = try std.fmt.bufPrint(&buf, "key-{d}", .{i});
|
||||
try std.testing.expect(bloom.mayContain(key));
|
||||
}
|
||||
}
|
||||
|
||||
test "bloom serialize roundtrip" {
|
||||
const allocator = std.testing.allocator;
|
||||
var bloom = try Bloom.init(allocator, 100, 0.01);
|
||||
defer bloom.deinit();
|
||||
bloom.add("alpha");
|
||||
bloom.add("bravo");
|
||||
|
||||
var out = std.ArrayList(u8).init(allocator);
|
||||
defer out.deinit();
|
||||
try bloom.serialize(encode.ByteWriter.init(&out));
|
||||
|
||||
var restored = try Bloom.deserialize(allocator, out.items);
|
||||
defer restored.deinit();
|
||||
try std.testing.expect(restored.mayContain("alpha"));
|
||||
try std.testing.expect(restored.mayContain("bravo"));
|
||||
try std.testing.expectEqual(bloom.bitCount, restored.bitCount);
|
||||
try std.testing.expectEqual(bloom.hashCount, restored.hashCount);
|
||||
}
|
||||
@@ -119,3 +119,51 @@ pub const BlockCache = struct {
|
||||
if (self.tail == null) self.tail = node;
|
||||
}
|
||||
};
|
||||
|
||||
test "block cache stores and serves" {
|
||||
const allocator = std.testing.allocator;
|
||||
var cache = BlockCache.init(allocator, 1024);
|
||||
defer cache.deinit();
|
||||
|
||||
const key = BlockKey{ .fileId = 1, .offset = 0 };
|
||||
try std.testing.expectEqual(@as(?[]const u8, null), cache.get(key));
|
||||
try std.testing.expectEqual(@as(u64, 1), cache.stats.misses);
|
||||
|
||||
try cache.put(key, "block-bytes");
|
||||
const got = cache.get(key).?;
|
||||
try std.testing.expectEqualStrings("block-bytes", got);
|
||||
try std.testing.expectEqual(@as(u64, 1), cache.stats.hits);
|
||||
}
|
||||
|
||||
test "block cache evicts least recently used" {
|
||||
const allocator = std.testing.allocator;
|
||||
var cache = BlockCache.init(allocator, 20);
|
||||
defer cache.deinit();
|
||||
|
||||
const a = BlockKey{ .fileId = 1, .offset = 0 };
|
||||
const b = BlockKey{ .fileId = 1, .offset = 100 };
|
||||
const c = BlockKey{ .fileId = 1, .offset = 200 };
|
||||
|
||||
try cache.put(a, "0123456789");
|
||||
try cache.put(b, "0123456789");
|
||||
_ = cache.get(a);
|
||||
try cache.put(c, "0123456789");
|
||||
|
||||
try std.testing.expect(cache.get(b) == null);
|
||||
try std.testing.expect(cache.get(a) != null);
|
||||
try std.testing.expect(cache.get(c) != null);
|
||||
}
|
||||
|
||||
test "block cache keeps most recent under tiny capacity" {
|
||||
const allocator = std.testing.allocator;
|
||||
var cache = BlockCache.init(allocator, 1);
|
||||
defer cache.deinit();
|
||||
|
||||
const a = BlockKey{ .fileId = 2, .offset = 0 };
|
||||
const b = BlockKey{ .fileId = 2, .offset = 50 };
|
||||
try cache.put(a, "big-payload");
|
||||
try cache.put(b, "big-payload");
|
||||
|
||||
try std.testing.expect(cache.get(a) == null);
|
||||
try std.testing.expect(cache.get(b) != null);
|
||||
}
|
||||
@@ -96,3 +96,74 @@ pub fn decode(allocator: Allocator, codec: Codec, payload: []const u8, originalL
|
||||
.rle => rleDecompress(allocator, payload, originalLen),
|
||||
};
|
||||
}
|
||||
|
||||
fn expectRoundtrip(allocator: Allocator, input: []const u8) !void {
|
||||
const compressed = try rleCompress(allocator, input);
|
||||
defer allocator.free(compressed);
|
||||
const restored = try rleDecompress(allocator, compressed, input.len);
|
||||
defer allocator.free(restored);
|
||||
try std.testing.expectEqualSlices(u8, input, restored);
|
||||
}
|
||||
|
||||
test "rle roundtrip on runs literals and mixed" {
|
||||
const allocator = std.testing.allocator;
|
||||
try expectRoundtrip(allocator, "");
|
||||
try expectRoundtrip(allocator, "a");
|
||||
try expectRoundtrip(allocator, "aaaaaaaaaa");
|
||||
try expectRoundtrip(allocator, "abcdefghij");
|
||||
try expectRoundtrip(allocator, "aaabbbcccdddeeefff");
|
||||
try expectRoundtrip(allocator, "aaaaaXbbbbbYcccccZ");
|
||||
try expectRoundtrip(allocator, &[_]u8{0} ** 300);
|
||||
try expectRoundtrip(allocator, &[_]u8{0xff} ** 300);
|
||||
}
|
||||
|
||||
test "rle roundtrip on random data" {
|
||||
const allocator = std.testing.allocator;
|
||||
var prng = std.Random.DefaultPrng.init(0x1234abcd);
|
||||
const random = prng.random();
|
||||
var buf: [4096]u8 = undefined;
|
||||
var trial: usize = 0;
|
||||
while (trial < 40) : (trial += 1) {
|
||||
const len = random.intRangeAtMost(usize, 0, buf.len);
|
||||
for (buf[0..len]) |*byte| byte.* = random.intRangeAtMost(u8, 0, 5);
|
||||
try expectRoundtrip(allocator, buf[0..len]);
|
||||
}
|
||||
}
|
||||
|
||||
test "rle compresses repetitive payload" {
|
||||
const allocator = std.testing.allocator;
|
||||
const payload = "x" ** 1000;
|
||||
const compressed = try rleCompress(allocator, payload);
|
||||
defer allocator.free(compressed);
|
||||
try std.testing.expect(compressed.len < payload.len / 10);
|
||||
}
|
||||
|
||||
test "encode picks smaller representation" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const compressible = "y" ** 500;
|
||||
const enc1 = try encode(allocator, compressible);
|
||||
defer enc1.deinit(allocator);
|
||||
try std.testing.expectEqual(Codec.rle, enc1.codec);
|
||||
const back1 = try decode(allocator, enc1.codec, enc1.bytes, compressible.len);
|
||||
defer allocator.free(back1);
|
||||
try std.testing.expectEqualSlices(u8, compressible, back1);
|
||||
|
||||
var incompressible: [256]u8 = undefined;
|
||||
var prng = std.Random.DefaultPrng.init(0xfeed);
|
||||
prng.random().bytes(&incompressible);
|
||||
const enc2 = try encode(allocator, &incompressible);
|
||||
defer enc2.deinit(allocator);
|
||||
try std.testing.expectEqual(Codec.none, enc2.codec);
|
||||
const back2 = try decode(allocator, enc2.codec, enc2.bytes, incompressible.len);
|
||||
defer allocator.free(back2);
|
||||
try std.testing.expectEqualSlices(u8, &incompressible, back2);
|
||||
}
|
||||
|
||||
test "decompress rejects truncated stream" {
|
||||
const allocator = std.testing.allocator;
|
||||
const payload = "z" ** 200;
|
||||
const compressed = try rleCompress(allocator, payload);
|
||||
defer allocator.free(compressed);
|
||||
try std.testing.expectError(error.CorruptBlock, rleDecompress(allocator, compressed[0 .. compressed.len - 1], payload.len));
|
||||
}
|
||||
@@ -17,3 +17,17 @@ pub const BlockCache = @import("cache.zig").BlockCache;
|
||||
pub const compress = @import("compress.zig");
|
||||
pub const Transaction = @import("txn.zig").Transaction;
|
||||
pub const Stats = @import("stats.zig").Stats;
|
||||
|
||||
test {
|
||||
_ = @import("encode.zig");
|
||||
_ = @import("bloom.zig");
|
||||
_ = @import("memtable.zig");
|
||||
_ = @import("wal.zig");
|
||||
_ = @import("sstable.zig");
|
||||
_ = @import("cache.zig");
|
||||
_ = @import("batch.zig");
|
||||
_ = @import("compress.zig");
|
||||
_ = @import("txn.zig");
|
||||
_ = @import("stats.zig");
|
||||
_ = @import("db.zig");
|
||||
}
|
||||
@@ -46,3 +46,20 @@ pub const Stats = struct {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test "stats hit rate and deepest level" {
|
||||
var stats = Stats{};
|
||||
stats.cacheHits = 3;
|
||||
stats.cacheMisses = 1;
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 0.75), stats.cacheHitRate(), 1e-9);
|
||||
|
||||
stats.levels[0].tables = 2;
|
||||
stats.levels[2].tables = 1;
|
||||
try std.testing.expectEqual(@as(usize, 2), stats.deepestLevel());
|
||||
}
|
||||
|
||||
test "empty stats hit rate is zero" {
|
||||
const stats = Stats{};
|
||||
try std.testing.expectEqual(@as(f64, 0), stats.cacheHitRate());
|
||||
try std.testing.expectEqual(@as(usize, 0), stats.deepestLevel());
|
||||
}
|
||||
Reference in new issue
Block a user