test(store): add point, flush, recovery, and batch tests

This commit is contained in:
dualfroz committed 2026-08-28 09:00:00 +02:00
1 parent c94bc91946
commit ffc4fadc47
1 file changed
+263
+263
View File
@@ -885,3 +885,266 @@ fn parseSstId(name: []const u8) ?u64 {
return std.fmt.parseInt(u64, digits, 10) catch null;
}
fn openTemp(tmp: *std.testing.TmpDir, allocator: Allocator, options: Options) !Db {
var pathBuf: [std.fs.max_path_bytes]u8 = undefined;
const base = try tmp.dir.realpath(".", &pathBuf);
var dbPathBuf: [std.fs.max_path_bytes]u8 = undefined;
const dbPath = try std.fmt.bufPrint(&dbPathBuf, "{s}/store", .{base});
return Db.open(allocator, dbPath, options);
}
test "put get delete" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
try db.put("apple", "red");
try db.put("banana", "yellow");
const apple = (try db.get(allocator, "apple")).?;
defer allocator.free(apple);
try std.testing.expectEqualStrings("red", apple);
try db.put("apple", "green");
const apple2 = (try db.get(allocator, "apple")).?;
defer allocator.free(apple2);
try std.testing.expectEqualStrings("green", apple2);
try db.delete("apple");
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "apple"));
const banana = (try db.get(allocator, "banana")).?;
defer allocator.free(banana);
try std.testing.expectEqualStrings("yellow", banana);
}
test "wal recovery after reopen" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
try db.put("city", "warsaw");
try db.put("river", "vistula");
try db.delete("city");
try std.testing.expectEqual(@as(usize, 0), db.sstables.items.len);
}
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
try std.testing.expectEqual(@as(usize, 0), db.sstables.items.len);
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "city"));
const river = (try db.get(allocator, "river")).?;
defer allocator.free(river);
try std.testing.expectEqualStrings("vistula", river);
}
test "flush to sstable and reopen" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 64 });
defer db.close();
var buf: [16]u8 = undefined;
var i: usize = 0;
while (i < 200) : (i += 1) {
const key = try std.fmt.bufPrint(&buf, "k{d:0>5}", .{i});
try db.put(key, "value-payload");
}
try std.testing.expect(db.sstables.items.len > 0);
}
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 64 });
defer db.close();
var buf: [16]u8 = undefined;
var i: usize = 0;
while (i < 200) : (i += 1) {
const key = try std.fmt.bufPrint(&buf, "k{d:0>5}", .{i});
const got = (try db.get(allocator, key)).?;
defer allocator.free(got);
try std.testing.expectEqualStrings("value-payload", got);
}
}
test "compaction dedups and drops tombstones" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 1 });
defer db.close();
try db.put("a", "1");
try db.put("b", "2");
try db.put("c", "3");
try db.put("d", "4");
try db.put("a", "11");
try db.delete("c");
try db.flush();
try std.testing.expect(db.sstables.items.len > 1);
try db.compact();
try std.testing.expectEqual(@as(usize, 1), db.sstables.items.len);
const a = (try db.get(allocator, "a")).?;
defer allocator.free(a);
try std.testing.expectEqualStrings("11", a);
const b = (try db.get(allocator, "b")).?;
defer allocator.free(b);
try std.testing.expectEqualStrings("2", b);
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "c"));
const d = (try db.get(allocator, "d")).?;
defer allocator.free(d);
try std.testing.expectEqualStrings("4", d);
}
test "scan merges memtable and sstable with tombstones" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
try db.put("a", "1");
try db.put("b", "2");
try db.put("c", "3");
try db.put("d", "4");
try db.put("e", "5");
try db.flush();
try db.put("c", "cNew");
try db.delete("b");
var iter = try db.scan(allocator, null, null);
defer iter.deinit();
const expectedKeys = [_][]const u8{ "a", "c", "d", "e" };
const expectedVals = [_][]const u8{ "1", "cNew", "4", "5" };
var idx: usize = 0;
while (try iter.next()) |item| {
try std.testing.expect(idx < expectedKeys.len);
try std.testing.expectEqualStrings(expectedKeys[idx], item.key);
try std.testing.expectEqualStrings(expectedVals[idx], item.value);
idx += 1;
}
try std.testing.expectEqual(expectedKeys.len, idx);
}
test "scan honors range bounds" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator, .{ .flushThreshold = 32 });
defer db.close();
var keyBuf: [8]u8 = undefined;
var i: usize = 0;
while (i < 26) : (i += 1) {
const key = try std.fmt.bufPrint(&keyBuf, "{c}", .{@as(u8, 'a') + @as(u8, @intCast(i))});
try db.put(key, "x");
}
var iter = try db.scan(allocator, "d", "h");
defer iter.deinit();
const expected = [_][]const u8{ "d", "e", "f", "g" };
var idx: usize = 0;
while (try iter.next()) |item| {
try std.testing.expect(idx < expected.len);
try std.testing.expectEqualStrings(expected[idx], item.key);
idx += 1;
}
try std.testing.expectEqual(expected.len, idx);
}
test "snapshot reads pre-overwrite value" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
try db.put("color", "red");
try db.put("shape", "circle");
var snap = try db.snapshot();
try db.put("color", "green");
try db.delete("shape");
try db.put("color", "blue");
const snapColor = (try snap.get(allocator, "color")).?;
defer allocator.free(snapColor);
try std.testing.expectEqualStrings("red", snapColor);
const snapShape = (try snap.get(allocator, "shape")).?;
defer allocator.free(snapShape);
try std.testing.expectEqualStrings("circle", snapShape);
const liveColor = (try db.get(allocator, "color")).?;
defer allocator.free(liveColor);
try std.testing.expectEqualStrings("blue", liveColor);
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "shape"));
var snapIter = try snap.scan(allocator, null, null);
defer snapIter.deinit();
var snapCount: usize = 0;
while (try snapIter.next()) |entry| {
if (std.mem.eql(u8, entry.key, "color")) try std.testing.expectEqualStrings("red", entry.value);
snapCount += 1;
}
try std.testing.expectEqual(@as(usize, 2), snapCount);
}
test "write batch applies all or nothing" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
{
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
try db.put("existing", "keep");
const seqBefore = db.nextSeq;
var batch = WriteBatch.init(allocator);
defer batch.deinit();
try batch.put("one", "1");
try batch.put("two", "2");
try batch.put("three", "3");
try batch.delete("existing");
try db.apply(&batch);
try std.testing.expectEqual(seqBefore + 4, db.nextSeq);
const one = (try db.get(allocator, "one")).?;
defer allocator.free(one);
try std.testing.expectEqualStrings("1", one);
const three = (try db.get(allocator, "three")).?;
defer allocator.free(three);
try std.testing.expectEqualStrings("3", three);
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "existing"));
}
var db = try openTemp(&tmp, allocator, .{});
defer db.close();
const two = (try db.get(allocator, "two")).?;
defer allocator.free(two);
try std.testing.expectEqualStrings("2", two);
try std.testing.expectEqual(@as(?[]u8, null), try db.get(allocator, "existing"));
}