feat: add generation-based cache, Stats, HasInterest, NumInterest, RemoveBatch, All, ReverseMatch to SubList

This commit is contained in:
Joseph Doherty
2026-02-23 00:45:28 -05:00
parent 4ad821394b
commit cc0fe04f3c
3 changed files with 562 additions and 173 deletions

View File

@@ -156,4 +156,125 @@ public class SubListTests
var r = sl.Match("foo.bar.baz");
r.PlainSubs.Length.ShouldBe(3);
}
[Fact]
public void Stats_returns_correct_values()
{
var sl = new SubList();
sl.Insert(MakeSub("foo.bar", sid: "1"));
sl.Insert(MakeSub("foo.baz", sid: "2"));
sl.Match("foo.bar");
sl.Match("foo.bar"); // cache hit
var stats = sl.Stats();
stats.NumSubs.ShouldBe(2u);
stats.NumInserts.ShouldBe(2ul);
stats.NumMatches.ShouldBe(2ul);
stats.CacheHitRate.ShouldBeGreaterThan(0.0);
}
[Fact]
public void HasInterest_returns_true_when_subscribers_exist()
{
var sl = new SubList();
sl.Insert(MakeSub("foo.bar"));
sl.HasInterest("foo.bar").ShouldBeTrue();
sl.HasInterest("foo.baz").ShouldBeFalse();
}
[Fact]
public void HasInterest_with_wildcards()
{
var sl = new SubList();
sl.Insert(MakeSub("foo.*"));
sl.HasInterest("foo.bar").ShouldBeTrue();
sl.HasInterest("bar.baz").ShouldBeFalse();
}
[Fact]
public void NumInterest_counts_subscribers()
{
var sl = new SubList();
sl.Insert(MakeSub("foo.bar", sid: "1"));
sl.Insert(MakeSub("foo.*", sid: "2"));
sl.Insert(MakeSub("foo.bar", queue: "q1", sid: "3"));
var (np, nq) = sl.NumInterest("foo.bar");
np.ShouldBe(2); // foo.bar + foo.*
nq.ShouldBe(1); // queue sub
}
[Fact]
public void RemoveBatch_removes_all()
{
var sl = new SubList();
var sub1 = MakeSub("foo.bar", sid: "1");
var sub2 = MakeSub("foo.baz", sid: "2");
var sub3 = MakeSub("bar.qux", sid: "3");
sl.Insert(sub1);
sl.Insert(sub2);
sl.Insert(sub3);
sl.Count.ShouldBe(3u);
sl.RemoveBatch([sub1, sub2]);
sl.Count.ShouldBe(1u);
sl.Match("foo.bar").PlainSubs.ShouldBeEmpty();
sl.Match("bar.qux").PlainSubs.ShouldHaveSingleItem();
}
[Fact]
public void All_returns_every_subscription()
{
var sl = new SubList();
var sub1 = MakeSub("foo.bar", sid: "1");
var sub2 = MakeSub("foo.*", sid: "2");
var sub3 = MakeSub("bar.>", queue: "q", sid: "3");
sl.Insert(sub1);
sl.Insert(sub2);
sl.Insert(sub3);
var all = sl.All();
all.Count.ShouldBe(3);
all.ShouldContain(sub1);
all.ShouldContain(sub2);
all.ShouldContain(sub3);
}
[Fact]
public void ReverseMatch_finds_patterns_matching_literal()
{
var sl = new SubList();
var sub1 = MakeSub("foo.bar", sid: "1");
var sub2 = MakeSub("foo.*", sid: "2");
var sub3 = MakeSub("foo.>", sid: "3");
var sub4 = MakeSub("bar.baz", sid: "4");
sl.Insert(sub1);
sl.Insert(sub2);
sl.Insert(sub3);
sl.Insert(sub4);
var result = sl.ReverseMatch("foo.bar");
result.PlainSubs.Length.ShouldBe(3); // foo.bar, foo.*, foo.>
result.PlainSubs.ShouldContain(sub1);
result.PlainSubs.ShouldContain(sub2);
result.PlainSubs.ShouldContain(sub3);
}
[Fact]
public void Generation_ID_invalidates_cache()
{
var sl = new SubList();
sl.Insert(MakeSub("foo.bar", sid: "1"));
// Prime cache
var r1 = sl.Match("foo.bar");
r1.PlainSubs.Length.ShouldBe(1);
// Insert another sub (bumps generation)
sl.Insert(MakeSub("foo.bar", sid: "2"));
// Cache should be invalidated by generation mismatch
var r2 = sl.Match("foo.bar");
r2.PlainSubs.Length.ShouldBe(2);
}
}