feat: add openTemp to collttl
This commit is contained in:
1 parent
69af18c2f0
commit
11230ca97c
1 file changed
+182
+182
@@ -170,3 +170,185 @@ pub fn sweepExpired(db: *Db, allocator: Allocator, nowNanos: i128) !usize {
|
||||
return due.items.len;
|
||||
}
|
||||
|
||||
fn openTemp(tmp: *std.testing.TmpDir, allocator: Allocator) !Db {
|
||||
const dbPath = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(dbPath);
|
||||
return Db.open(allocator, dbPath, .{});
|
||||
}
|
||||
|
||||
test "remaining reports noKey for a collection that was never created" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
try std.testing.expectEqual(TtlResult.noKey, try remaining(&db, allocator, .hash, "missing", 0));
|
||||
}
|
||||
|
||||
test "remaining reports noExpiry then counted seconds for a real hash" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
_ = try collmod.Hash.set(&db, allocator, "profile", "name", "ada");
|
||||
try std.testing.expectEqual(TtlResult.noExpiry, try remaining(&db, allocator, .hash, "profile", 0));
|
||||
|
||||
try setExpireSeconds(&db, allocator, .hash, "profile", 1000, 30);
|
||||
const r = try remaining(&db, allocator, .hash, "profile", 1000);
|
||||
try std.testing.expectEqual(@as(u64, 30), r.seconds);
|
||||
}
|
||||
|
||||
test "expireIfDue drops every field of a hash, not just one key" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
_ = try collmod.Hash.set(&db, allocator, "session:1", "user", "ada");
|
||||
_ = try collmod.Hash.set(&db, allocator, "session:1", "role", "admin");
|
||||
try setExpireSeconds(&db, allocator, .hash, "session:1", 0, 5);
|
||||
|
||||
try std.testing.expect(!try expireIfDue(&db, allocator, .hash, "session:1", 4 * ttlmod.secondNanos));
|
||||
try std.testing.expectEqual(@as(usize, 2), try collmod.Hash.len(&db, allocator, "session:1"));
|
||||
|
||||
try std.testing.expect(try expireIfDue(&db, allocator, .hash, "session:1", 5 * ttlmod.secondNanos));
|
||||
try std.testing.expectEqual(@as(usize, 0), try collmod.Hash.len(&db, allocator, "session:1"));
|
||||
try std.testing.expect(!try collmod.collectionExists(&db, allocator, .hash, "session:1"));
|
||||
try std.testing.expectEqual(@as(?i128, null), try getExpiresAt(&db, allocator, .hash, "session:1"));
|
||||
}
|
||||
|
||||
test "expireIfDueMany reaps only the due names among several, mirroring a Set.intersect call" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
_ = try collmod.Set.add(&db, allocator, "a", "x");
|
||||
_ = try collmod.Set.add(&db, allocator, "b", "x");
|
||||
_ = try collmod.Set.add(&db, allocator, "c", "x");
|
||||
try setExpireSeconds(&db, allocator, .set, "b", 0, 5);
|
||||
|
||||
// Mirrors `coll set inter a b c`: the CLI's blanket lazy check only
|
||||
// ever covered args[0] ("a"); this call is what now covers args[1..].
|
||||
const reaped = try expireIfDueMany(&db, allocator, .set, &.{ "b", "c" }, 5 * ttlmod.secondNanos);
|
||||
try std.testing.expectEqual(@as(usize, 1), reaped);
|
||||
|
||||
try std.testing.expect(!try collmod.collectionExists(&db, allocator, .set, "b"));
|
||||
try std.testing.expect(try collmod.collectionExists(&db, allocator, .set, "a"));
|
||||
try std.testing.expect(try collmod.collectionExists(&db, allocator, .set, "c"));
|
||||
|
||||
// Now Set.intersect(a, c) sees a live pair and finds the real member,
|
||||
// instead of silently intersecting against a phantom "b" that a
|
||||
// periodic sweep hadn't reaped yet.
|
||||
const inter = try collmod.Set.intersect(&db, allocator, &.{ "a", "c" });
|
||||
defer {
|
||||
for (inter) |m| allocator.free(m);
|
||||
allocator.free(inter);
|
||||
}
|
||||
try std.testing.expectEqual(@as(usize, 1), inter.len);
|
||||
try std.testing.expectEqualStrings("x", inter[0]);
|
||||
}
|
||||
|
||||
test "expireIfDueMany is a no-op count of zero when nothing is due" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
_ = try collmod.Set.add(&db, allocator, "a", "x");
|
||||
_ = try collmod.Set.add(&db, allocator, "b", "x");
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), try expireIfDueMany(&db, allocator, .set, &.{ "a", "b" }, 0));
|
||||
}
|
||||
|
||||
test "clearExpire makes a collection immortal again" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
try collmod.List.pushRight(&db, allocator, "queue", "job-1");
|
||||
try setExpireSeconds(&db, allocator, .list, "queue", 0, 1);
|
||||
try clearExpire(&db, allocator, .list, "queue");
|
||||
|
||||
try std.testing.expectEqual(TtlResult.noExpiry, try remaining(&db, allocator, .list, "queue", 100 * ttlmod.secondNanos));
|
||||
try std.testing.expect(!try expireIfDue(&db, allocator, .list, "queue", 100 * ttlmod.secondNanos));
|
||||
try std.testing.expectEqual(@as(usize, 1), try collmod.List.len(&db, allocator, "queue"));
|
||||
}
|
||||
|
||||
test "sweepExpired reaps due collections of every kind without touching live ones" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
_ = try collmod.Counter.incrBy(&db, allocator, "hits", 3);
|
||||
try setExpireSeconds(&db, allocator, .counter, "hits", 0, 1);
|
||||
|
||||
_ = try collmod.Hash.set(&db, allocator, "profile", "name", "ada");
|
||||
try setExpireSeconds(&db, allocator, .hash, "profile", 0, 100);
|
||||
|
||||
try collmod.ZSet.add(&db, allocator, "leaderboard", "alice", 10);
|
||||
try setExpireSeconds(&db, allocator, .zset, "leaderboard", 0, 1);
|
||||
|
||||
_ = try collmod.Set.add(&db, allocator, "tags", "zig");
|
||||
|
||||
const reaped = try sweepExpired(&db, allocator, 2 * ttlmod.secondNanos);
|
||||
try std.testing.expectEqual(@as(usize, 2), reaped);
|
||||
|
||||
try std.testing.expect(!try collmod.collectionExists(&db, allocator, .counter, "hits"));
|
||||
try std.testing.expect(!try collmod.collectionExists(&db, allocator, .zset, "leaderboard"));
|
||||
try std.testing.expect(try collmod.collectionExists(&db, allocator, .hash, "profile"));
|
||||
try std.testing.expect(try collmod.collectionExists(&db, allocator, .set, "tags"));
|
||||
|
||||
// Idempotent: a second sweep at the same instant finds nothing left.
|
||||
try std.testing.expectEqual(@as(usize, 0), try sweepExpired(&db, allocator, 2 * ttlmod.secondNanos));
|
||||
}
|
||||
|
||||
test "collection sweep survives a wal reopen: markers are durable" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
{
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
_ = try collmod.Hash.set(&db, allocator, "durable", "k", "v");
|
||||
try setExpireSeconds(&db, allocator, .hash, "durable", 0, 5);
|
||||
}
|
||||
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
const r = try remaining(&db, allocator, .hash, "durable", 1 * ttlmod.secondNanos);
|
||||
try std.testing.expectEqual(@as(u64, 4), r.seconds);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), try sweepExpired(&db, allocator, 10 * ttlmod.secondNanos));
|
||||
try std.testing.expect(!try collmod.collectionExists(&db, allocator, .hash, "durable"));
|
||||
}
|
||||
|
||||
test "markers for the same name under different kinds never collide" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var db = try openTemp(&tmp, allocator);
|
||||
defer db.close();
|
||||
|
||||
_ = try collmod.Hash.set(&db, allocator, "shared", "f", "v");
|
||||
_ = try collmod.Set.add(&db, allocator, "shared", "m");
|
||||
|
||||
try setExpireSeconds(&db, allocator, .hash, "shared", 0, 5);
|
||||
// The set named "shared" must still read as noExpiry - the marker for
|
||||
// the hash "shared" must not leak across the kind boundary.
|
||||
try std.testing.expectEqual(TtlResult.noExpiry, try remaining(&db, allocator, .set, "shared", 0));
|
||||
|
||||
const hashResult = try remaining(&db, allocator, .hash, "shared", 0);
|
||||
try std.testing.expectEqual(@as(u64, 5), hashResult.seconds);
|
||||
}
|
||||
Reference in new issue
Block a user