feat: add IncrCtx to server

This commit is contained in:
dualfroz committed 2026-09-12 15:33:51 +02:00
1 parent 8f9376fed0
commit aa1e1992e4
1 file changed
+153
+153
View File
@@ -6600,3 +6600,156 @@ test "coll_op over the wire exercises all five collection kinds against a real s
defer got.deinit(allocator); defer got.deinit(allocator);
try std.testing.expectEqualStrings("grace", got.bytes); try std.testing.expectEqualStrings("grace", got.bytes);
var missing = try client.collOp(allocator, .hash_get, "profile", &[_][]const u8{"ghost"});
defer missing.deinit(allocator);
try std.testing.expect(missing == .nil);
var lenReply = try client.collOp(allocator, .hash_len, "profile", &.{});
defer lenReply.deinit(allocator);
try std.testing.expectEqual(@as(i64, 2), lenReply.int);
var delReply = try client.collOp(allocator, .hash_del, "profile", &[_][]const u8{"age"});
defer delReply.deinit(allocator);
try std.testing.expectEqual(@as(i64, 1), delReply.int);
var all = try client.collOp(allocator, .hash_getall, "profile", &.{});
defer all.deinit(allocator);
try std.testing.expectEqual(@as(usize, 1), all.pairList.len);
try std.testing.expectEqualStrings("name", all.pairList[0].field);
try std.testing.expectEqualStrings("grace", all.pairList[0].value);
}
// Set: add/rem/ismember/card/members plus the three multi-set ops,
// where `name` is the first set and the extra `args` are the rest -
// the same split `runColl` uses locally for `args[0]` vs `args[1..]`.
{
_ = try client.collOp(allocator, .set_add, "a", &[_][]const u8{"x"});
_ = try client.collOp(allocator, .set_add, "a", &[_][]const u8{"y"});
_ = try client.collOp(allocator, .set_add, "b", &[_][]const u8{"y"});
_ = try client.collOp(allocator, .set_add, "b", &[_][]const u8{"z"});
var isMem = try client.collOp(allocator, .set_ismember, "a", &[_][]const u8{"x"});
defer isMem.deinit(allocator);
try std.testing.expectEqual(@as(i64, 1), isMem.int);
var card = try client.collOp(allocator, .set_card, "a", &.{});
defer card.deinit(allocator);
try std.testing.expectEqual(@as(i64, 2), card.int);
var inter = try client.collOp(allocator, .set_inter, "a", &[_][]const u8{"b"});
defer inter.deinit(allocator);
try std.testing.expectEqual(@as(usize, 1), inter.stringList.len);
try std.testing.expectEqualStrings("y", inter.stringList[0]);
var uni = try client.collOp(allocator, .set_union, "a", &[_][]const u8{"b"});
defer uni.deinit(allocator);
try std.testing.expectEqual(@as(usize, 3), uni.stringList.len);
var diff = try client.collOp(allocator, .set_diff, "a", &[_][]const u8{"b"});
defer diff.deinit(allocator);
try std.testing.expectEqual(@as(usize, 1), diff.stringList.len);
try std.testing.expectEqualStrings("x", diff.stringList[0]);
var rem = try client.collOp(allocator, .set_rem, "a", &[_][]const u8{"x"});
defer rem.deinit(allocator);
try std.testing.expectEqual(@as(i64, 1), rem.int);
}
// List: push both ends, pop both ends, range with a redis-style
// negative index, len reflecting what remains.
{
_ = try client.collOp(allocator, .list_rpush, "queue", &[_][]const u8{"one"});
_ = try client.collOp(allocator, .list_rpush, "queue", &[_][]const u8{"two"});
_ = try client.collOp(allocator, .list_lpush, "queue", &[_][]const u8{"zero"});
var ranged = try client.collOp(allocator, .list_range, "queue", &[_][]const u8{ "0", "-1" });
defer ranged.deinit(allocator);
try std.testing.expectEqual(@as(usize, 3), ranged.stringList.len);
try std.testing.expectEqualStrings("zero", ranged.stringList[0]);
try std.testing.expectEqualStrings("two", ranged.stringList[2]);
var popped = try client.collOp(allocator, .list_lpop, "queue", &.{});
defer popped.deinit(allocator);
try std.testing.expectEqualStrings("zero", popped.bytes);
var lenReply = try client.collOp(allocator, .list_len, "queue", &.{});
defer lenReply.deinit(allocator);
try std.testing.expectEqual(@as(i64, 2), lenReply.int);
var emptyPop = try client.collOp(allocator, .list_rpop, "never-existed", &.{});
defer emptyPop.deinit(allocator);
try std.testing.expect(emptyPop == .nil);
}
// ZSet: add/incrby/score/rank/card/rangebyrank/rangebyscore, with a
// real fractional score to exercise the f64 wire encoding end to end.
{
_ = try client.collOp(allocator, .zset_add, "board", &[_][]const u8{ "alice", "10.5" });
_ = try client.collOp(allocator, .zset_add, "board", &[_][]const u8{ "bob", "20" });
var score = try client.collOp(allocator, .zset_score, "board", &[_][]const u8{"alice"});
defer score.deinit(allocator);
try std.testing.expectEqual(@as(f64, 10.5), score.real);
var incr = try client.collOp(allocator, .zset_incrby, "board", &[_][]const u8{ "alice", "1.5" });
defer incr.deinit(allocator);
try std.testing.expectEqual(@as(f64, 12.0), incr.real);
var rank = try client.collOp(allocator, .zset_rank, "board", &[_][]const u8{"alice"});
defer rank.deinit(allocator);
try std.testing.expectEqual(@as(i64, 0), rank.int);
var byRank = try client.collOp(allocator, .zset_rangebyrank, "board", &[_][]const u8{ "0", "-1" });
defer byRank.deinit(allocator);
try std.testing.expectEqual(@as(usize, 2), byRank.scorePairList.len);
try std.testing.expectEqualStrings("alice", byRank.scorePairList[0].member);
try std.testing.expectEqualStrings("bob", byRank.scorePairList[1].member);
var byScore = try client.collOp(allocator, .zset_rangebyscore, "board", &[_][]const u8{ "15", "25" });
defer byScore.deinit(allocator);
try std.testing.expectEqual(@as(usize, 1), byScore.scorePairList.len);
try std.testing.expectEqualStrings("bob", byScore.scorePairList[0].member);
var card = try client.collOp(allocator, .zset_card, "board", &.{});
defer card.deinit(allocator);
try std.testing.expectEqual(@as(i64, 2), card.int);
var removed = try client.collOp(allocator, .zset_rem, "board", &[_][]const u8{"bob"});
defer removed.deinit(allocator);
try std.testing.expectEqual(@as(i64, 1), removed.int);
}
}
// Closes the gap every `coll_op` session note since #30 has flagged and
// deferred: every existing `coll_op` test drives ONE connection at a
// time, so nothing has ever proven that many independent TCP connections
// hammering `coll_op` against the SAME named collection concurrently are
// safe - `Server.dispatch` takes `self.mutex` once per request exactly
// like every other opcode, but that claim had never been exercised here
// with real OS threads and real sockets the way `concurrent.zig`'s
// `GuardedDb` tests already exercise `Db`/`TableTxn` directly.
test "coll_op survives many real concurrent connections hammering the same counter" {
const allocator = std.testing.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = try tmp.dir.realpathAlloc(allocator, ".");
defer allocator.free(path);
var db = try testDb(allocator, path);
defer db.close();
const address = try std.net.Address.parseIp("127.0.0.1", 0);
var server = try Server.init(allocator, &db, address);
try server.start();
defer server.stop();
const client_mod = @import("client.zig");
const threadCount = 8;
const perThread = 50;
const IncrCtx = struct {
allocator: Allocator,
addr: std.net.Address,
ok: bool = false,
};