Port trie-based subject matching engine (81 features, 74 tests). Includes SubscriptionIndex with cache, wildcard matching (*/>), queue subscription groups, reverse match, notifications, stats, and subject validation utilities. Also adds minimal Subscription and NatsClient stubs needed by the index.
1041 lines
38 KiB
C#
1041 lines
38 KiB
C#
// Copyright 2012-2025 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
using System.Text;
|
|
using Shouldly;
|
|
using ZB.MOM.NatsNet.Server.Internal;
|
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
|
|
|
namespace ZB.MOM.NatsNet.Server.Tests.Internal.DataStructures;
|
|
|
|
/// <summary>
|
|
/// Tests for SubscriptionIndex — mirrors tests from server/sublist_test.go.
|
|
/// Cache/no-cache pairs are collapsed into Theory with InlineData.
|
|
/// </summary>
|
|
public class SubscriptionIndexTests
|
|
{
|
|
// Helper factories matching Go's newSub/newQSub/newRemoteQSub.
|
|
private static Subscription NewSub(string subject) =>
|
|
new() { Subject = Encoding.ASCII.GetBytes(subject), Client = new NatsClient { Kind = ClientKind.Client } };
|
|
|
|
private static Subscription NewQSub(string subject, string queue) =>
|
|
queue.Length > 0
|
|
? new() { Subject = Encoding.ASCII.GetBytes(subject), Queue = Encoding.ASCII.GetBytes(queue) }
|
|
: NewSub(subject);
|
|
|
|
private static Subscription NewRemoteQSub(string subject, string queue, int weight) =>
|
|
queue.Length > 0
|
|
? new() { Subject = Encoding.ASCII.GetBytes(subject), Queue = Encoding.ASCII.GetBytes(queue), Qw = weight, Client = new NatsClient { Kind = ClientKind.Router } }
|
|
: NewSub(subject);
|
|
|
|
private static SubscriptionIndex MakeSl(bool cache) =>
|
|
cache ? SubscriptionIndex.NewSublistWithCache() : SubscriptionIndex.NewSublistNoCache();
|
|
|
|
private static void VerifyMember(List<Subscription> subs, Subscription expected)
|
|
{
|
|
subs.ShouldContain(expected, $"Subscription for [{Encoding.ASCII.GetString(expected.Subject)}] not found in results");
|
|
}
|
|
|
|
private static void VerifyQMember(List<List<Subscription>> qsubs, Subscription expected)
|
|
{
|
|
var idx = SubscriptionIndex.FindQSlot(expected.Queue, qsubs);
|
|
idx.ShouldBeGreaterThanOrEqualTo(0);
|
|
qsubs[idx].ShouldContain(expected);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Init & Count (T:2962-2964)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:2962
|
|
public void Init_StartsWithZeroCount()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
s.Count().ShouldBe(0u);
|
|
}
|
|
|
|
[Theory] // T:2963, T:2964
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void InsertCount_ReturnsCorrectCount(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
s.Insert(NewSub("foo"));
|
|
s.Insert(NewSub("bar"));
|
|
s.Insert(NewSub("foo.bar"));
|
|
s.Count().ShouldBe(3u);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Simple Match (T:2965-2968)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:2965, T:2966
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void Simple_SingleTokenMatch(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("foo");
|
|
s.Insert(sub);
|
|
var r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
VerifyMember(r.PSubs, sub);
|
|
}
|
|
|
|
[Theory] // T:2967, T:2968
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void Simple_MultiTokenMatch(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("foo.bar.baz");
|
|
s.Insert(sub);
|
|
var r = s.Match("foo.bar.baz");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
VerifyMember(r.PSubs, sub);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Wildcard Match (T:2969-2974)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:2969, T:2970
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void PartialWildcard_MatchesCorrectly(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var lsub = NewSub("a.b.c");
|
|
var psub = NewSub("a.*.c");
|
|
s.Insert(lsub);
|
|
s.Insert(psub);
|
|
var r = s.Match("a.b.c");
|
|
r.PSubs.Count.ShouldBe(2);
|
|
VerifyMember(r.PSubs, lsub);
|
|
VerifyMember(r.PSubs, psub);
|
|
}
|
|
|
|
[Theory] // T:2971, T:2972
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void PartialWildcardAtEnd_MatchesCorrectly(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var lsub = NewSub("a.b.c");
|
|
var psub = NewSub("a.b.*");
|
|
s.Insert(lsub);
|
|
s.Insert(psub);
|
|
var r = s.Match("a.b.c");
|
|
r.PSubs.Count.ShouldBe(2);
|
|
VerifyMember(r.PSubs, lsub);
|
|
VerifyMember(r.PSubs, psub);
|
|
}
|
|
|
|
[Theory] // T:2973, T:2974
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void FullWildcard_MatchesAll(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var lsub = NewSub("a.b.c");
|
|
var fsub = NewSub("a.>");
|
|
s.Insert(lsub);
|
|
s.Insert(fsub);
|
|
var r = s.Match("a.b.c");
|
|
r.PSubs.Count.ShouldBe(2);
|
|
VerifyMember(r.PSubs, lsub);
|
|
VerifyMember(r.PSubs, fsub);
|
|
|
|
r = s.Match("a.>");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
VerifyMember(r.PSubs, fsub);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Remove (T:2975-2984)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:2975, T:2976
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void Remove_BasicRemoval(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("a.b.c.d");
|
|
s.Insert(sub);
|
|
s.Count().ShouldBe(1u);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(1);
|
|
s.Remove(NewSub("a.b.c")); // wrong sub, no-op
|
|
s.Count().ShouldBe(1u);
|
|
s.Remove(sub);
|
|
s.Count().ShouldBe(0u);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(0);
|
|
}
|
|
|
|
[Theory] // T:2977, T:2978
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RemoveWildcard_CorrectCountsAndMatch(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("a.b.c.d");
|
|
var psub = NewSub("a.b.*.d");
|
|
var fsub = NewSub("a.b.>");
|
|
s.Insert(sub); s.Insert(psub); s.Insert(fsub);
|
|
s.Count().ShouldBe(3u);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(3);
|
|
s.Remove(sub); s.Count().ShouldBe(2u);
|
|
s.Remove(fsub); s.Count().ShouldBe(1u);
|
|
s.Remove(psub); s.Count().ShouldBe(0u);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(0);
|
|
}
|
|
|
|
[Theory] // T:2979, T:2980
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RemoveCleanup_LevelsReclaimed(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("a.b.c.d.e.f");
|
|
s.NumLevels().ShouldBe(0);
|
|
s.Insert(sub);
|
|
s.NumLevels().ShouldBe(6);
|
|
s.Remove(sub);
|
|
s.NumLevels().ShouldBe(0);
|
|
}
|
|
|
|
[Theory] // T:2981, T:2982
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RemoveCleanupWildcards_LevelsReclaimed(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("a.b.*.d.e.>");
|
|
s.NumLevels().ShouldBe(0);
|
|
s.Insert(sub);
|
|
s.NumLevels().ShouldBe(6);
|
|
s.Remove(sub);
|
|
s.NumLevels().ShouldBe(0);
|
|
}
|
|
|
|
[Theory] // T:2983, T:2984
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RemoveWithLargeSubs_CorrectAfterRemoval(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
for (int i = 0; i < SubscriptionIndex.PlistMin * 2; i++)
|
|
s.Insert(NewSub("foo"));
|
|
var r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(SubscriptionIndex.PlistMin * 2);
|
|
|
|
s.Remove(r.PSubs[SubscriptionIndex.PlistMin]);
|
|
s.Remove(r.PSubs[0]);
|
|
s.Remove(r.PSubs[^1]);
|
|
r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(SubscriptionIndex.PlistMin * 2 - 3);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Invalid Subjects (T:2985, T:2986)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:2985, T:2986
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void InvalidSubjects_InsertReturnsError(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
s.Insert(NewSub(".foo")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.Insert(NewSub("foo.")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.Insert(NewSub("foo..bar")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.Insert(NewSub("foo.bar..baz")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.Insert(NewSub("foo.>.bar")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// RemoveBatch (T:2987, T:2988)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:2987
|
|
public void NoCacheRemoveBatch_DoesNotEnableCache()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistNoCache();
|
|
s.Insert(NewSub("foo"));
|
|
var sub = NewSub("bar");
|
|
s.Insert(sub);
|
|
s.RemoveBatch(new[] { sub });
|
|
for (int i = 0; i < 10; i++) s.Match("foo");
|
|
s.CacheEnabled().ShouldBeFalse();
|
|
}
|
|
|
|
[Fact] // T:2988
|
|
public void RemoveBatchWithError_StillRemovesPresent()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistNoCache();
|
|
var sub1 = NewSub("foo");
|
|
var sub2 = NewSub("bar");
|
|
var sub3 = NewSub("baz");
|
|
s.Insert(sub1); s.Insert(sub2); s.Insert(sub3);
|
|
var notPresent = NewSub("not.inserted");
|
|
var err = s.RemoveBatch(new[] { notPresent, sub1, sub3 });
|
|
err.ShouldBe(SubscriptionIndex.ErrNotFound);
|
|
s.Count().ShouldBe(1u);
|
|
s.Match("bar").PSubs.Count.ShouldBe(1);
|
|
VerifyMember(s.Match("bar").PSubs, sub2);
|
|
s.Match("foo").PSubs.Count.ShouldBe(0);
|
|
s.Match("baz").PSubs.Count.ShouldBe(0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Cache behavior (T:2989)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:2989
|
|
public void Cache_BasicBehavior()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
var sub = NewSub("a.b.c.d");
|
|
var psub = NewSub("a.b.*.d");
|
|
var fsub = NewSub("a.b.>");
|
|
|
|
s.Insert(sub);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(1);
|
|
s.Insert(psub); s.Insert(fsub);
|
|
s.Count().ShouldBe(3u);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(3);
|
|
s.Remove(sub); s.Count().ShouldBe(2u);
|
|
s.Remove(fsub); s.Count().ShouldBe(1u);
|
|
s.Remove(psub); s.Count().ShouldBe(0u);
|
|
s.CacheCount().ShouldBe(0);
|
|
s.Match("a.b.c.d").PSubs.Count.ShouldBe(0);
|
|
|
|
// Fill cache beyond max.
|
|
for (int i = 0; i < 2 * SubscriptionIndex.SlCacheMax; i++)
|
|
s.Match($"foo-{i}");
|
|
|
|
// Cache sweep runs async, wait briefly.
|
|
Thread.Sleep(200);
|
|
s.CacheCount().ShouldBeLessThanOrEqualTo(SubscriptionIndex.SlCacheMax);
|
|
|
|
// Test wildcard cache update.
|
|
s = SubscriptionIndex.NewSublistWithCache();
|
|
s.Insert(NewSub("foo.*"));
|
|
s.Insert(NewSub("foo.bar"));
|
|
s.Match("foo.baz").PSubs.Count.ShouldBe(1);
|
|
s.Match("foo.bar").PSubs.Count.ShouldBe(2);
|
|
s.Insert(NewSub("foo.>"));
|
|
s.Match("foo.bar").PSubs.Count.ShouldBe(3);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Queue Results (T:2990, T:2991)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:2990, T:2991
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void BasicQueueResults_CorrectGrouping(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("foo");
|
|
var sub1 = NewQSub("foo", "bar");
|
|
var sub2 = NewQSub("foo", "baz");
|
|
|
|
s.Insert(sub1);
|
|
var r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
r.QSubs.Count.ShouldBe(1);
|
|
r.QSubs[0].Count.ShouldBe(1);
|
|
VerifyQMember(r.QSubs, sub1);
|
|
|
|
s.Insert(sub2);
|
|
r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
r.QSubs.Count.ShouldBe(2);
|
|
VerifyQMember(r.QSubs, sub1);
|
|
VerifyQMember(r.QSubs, sub2);
|
|
|
|
s.Insert(sub);
|
|
r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
r.QSubs.Count.ShouldBe(2);
|
|
|
|
var sub3 = NewQSub("foo", "bar");
|
|
var sub4 = NewQSub("foo", "baz");
|
|
s.Insert(sub3); s.Insert(sub4);
|
|
r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
r.QSubs.Count.ShouldBe(2);
|
|
|
|
// Verify each group has 2 members.
|
|
var barIdx = SubscriptionIndex.FindQSlot(Encoding.ASCII.GetBytes("bar"), r.QSubs);
|
|
var bazIdx = SubscriptionIndex.FindQSlot(Encoding.ASCII.GetBytes("baz"), r.QSubs);
|
|
r.QSubs[barIdx].Count.ShouldBe(2);
|
|
r.QSubs[bazIdx].Count.ShouldBe(2);
|
|
|
|
// Removal.
|
|
s.Remove(sub);
|
|
r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
r.QSubs.Count.ShouldBe(2);
|
|
|
|
s.Remove(sub1);
|
|
r = s.Match("foo");
|
|
barIdx = SubscriptionIndex.FindQSlot(Encoding.ASCII.GetBytes("bar"), r.QSubs);
|
|
bazIdx = SubscriptionIndex.FindQSlot(Encoding.ASCII.GetBytes("baz"), r.QSubs);
|
|
r.QSubs[barIdx].Count.ShouldBe(1);
|
|
r.QSubs[bazIdx].Count.ShouldBe(2);
|
|
|
|
s.Remove(sub3); // last bar
|
|
r = s.Match("foo");
|
|
r.QSubs.Count.ShouldBe(1); // only baz remains
|
|
|
|
s.Remove(sub2); s.Remove(sub4);
|
|
r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
r.QSubs.Count.ShouldBe(0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Subject Validation (T:2992-2997)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:2992
|
|
public void ValidLiteralSubjects_CorrectResults()
|
|
{
|
|
SubscriptionIndex.IsValidLiteralSubject("foo").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject(".foo").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo..bar").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.bar.*").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.bar.>").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidLiteralSubject("*").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidLiteralSubject(">").ShouldBeFalse();
|
|
// Wildcard chars that aren't standalone tokens.
|
|
SubscriptionIndex.IsValidLiteralSubject("foo*").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo**").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.**").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo*bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.*bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo*.bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("*bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo>").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo>>").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.>>").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo>bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo.>bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject("foo>.bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidLiteralSubject(">bar").ShouldBeTrue();
|
|
}
|
|
|
|
[Fact] // T:2993
|
|
public void ValidSubjects_CorrectResults()
|
|
{
|
|
SubscriptionIndex.IsValidSubject(".").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidSubject(".foo").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidSubject("foo.").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidSubject("foo..bar").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidSubject(">.bar").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidSubject("foo.>.bar").ShouldBeFalse();
|
|
SubscriptionIndex.IsValidSubject("foo").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo.bar.*").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo.bar.>").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("*").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject(">").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo*").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo**").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo.**").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo*bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo.*bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo*.bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("*bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo>").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo.>>").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo>bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo.>bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject("foo>.bar").ShouldBeTrue();
|
|
SubscriptionIndex.IsValidSubject(">bar").ShouldBeTrue();
|
|
}
|
|
|
|
[Fact] // T:2994
|
|
public void MatchLiterals_CorrectResults()
|
|
{
|
|
SubscriptionIndex.MatchLiteral("foo", "foo").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo", "bar").ShouldBeFalse();
|
|
SubscriptionIndex.MatchLiteral("foo", "*").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo", ">").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo.bar", ">").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo.bar", "foo.>").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo.bar", "bar.>").ShouldBeFalse();
|
|
SubscriptionIndex.MatchLiteral("stats.test.22", "stats.>").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("stats.test.22", "stats.*.*").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo.bar", "foo").ShouldBeFalse();
|
|
SubscriptionIndex.MatchLiteral("stats.test.foos", "stats.test.foos").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("stats.test.foos", "stats.test.foo").ShouldBeFalse();
|
|
SubscriptionIndex.MatchLiteral("stats.test", "stats.test.*").ShouldBeFalse();
|
|
SubscriptionIndex.MatchLiteral("stats.test.foos", "stats.*").ShouldBeFalse();
|
|
SubscriptionIndex.MatchLiteral("stats.test.foos", "stats.*.*.foos").ShouldBeFalse();
|
|
// Wildcards as non-token literals.
|
|
SubscriptionIndex.MatchLiteral("*bar", "*bar").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo*", "foo*").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo*bar", "foo*bar").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo.***.bar", "foo.***.bar").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral(">bar", ">bar").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo>", "foo>").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo>bar", "foo>bar").ShouldBeTrue();
|
|
SubscriptionIndex.MatchLiteral("foo.>>>.bar", "foo.>>>.bar").ShouldBeTrue();
|
|
}
|
|
|
|
[Fact] // T:2995
|
|
public void SubjectIsLiteral_CorrectResults()
|
|
{
|
|
SubscriptionIndex.SubjectIsLiteral("foo").ShouldBeTrue();
|
|
SubscriptionIndex.SubjectIsLiteral("foo.bar").ShouldBeTrue();
|
|
SubscriptionIndex.SubjectIsLiteral("foo*.bar").ShouldBeTrue();
|
|
SubscriptionIndex.SubjectIsLiteral("*").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectIsLiteral(">").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectIsLiteral("foo.*").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectIsLiteral("foo.>").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectIsLiteral("foo.*.>").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectIsLiteral("foo.*.bar").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectIsLiteral("foo.bar.>").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact] // T:2997
|
|
public void TokenAt_ReturnsCorrectTokens()
|
|
{
|
|
SubscriptionIndex.TokenAt("foo.bar.baz.*", 0).ShouldBe(string.Empty);
|
|
SubscriptionIndex.TokenAt("foo.bar.baz.*", 1).ShouldBe("foo");
|
|
SubscriptionIndex.TokenAt("foo.bar.baz.*", 2).ShouldBe("bar");
|
|
SubscriptionIndex.TokenAt("foo.bar.baz.*", 3).ShouldBe("baz");
|
|
SubscriptionIndex.TokenAt("foo.bar.baz.*", 4).ShouldBe("*");
|
|
SubscriptionIndex.TokenAt("foo.bar.baz.*", 5).ShouldBe(string.Empty);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Bad Subject on Remove (T:2998, T:2999)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:2998, T:2999
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void BadSubjectOnRemove_ReturnsError(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
s.Insert(NewSub("a.b..d")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.Remove(NewSub("a.b..d")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.Remove(NewSub("a.>.b")).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Two-token pub vs single-token sub (T:3000, T:3001)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:3000, T:3001
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void TwoTokenPub_DoesNotMatchSingleTokenSub(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var sub = NewSub("foo");
|
|
s.Insert(sub);
|
|
s.Match("foo").PSubs.Count.ShouldBe(1);
|
|
s.Match("foo.bar").PSubs.Count.ShouldBe(0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Wildcards as literals (T:3002-3005)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:3002, T:3003
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void InsertWithWildcardsAsLiterals_OnlyExactMatch(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
foreach (var subject in new[] { "foo.*-", "foo.>-" })
|
|
{
|
|
var sub = NewSub(subject);
|
|
s.Insert(sub);
|
|
s.Match("foo.bar").PSubs.Count.ShouldBe(0);
|
|
s.Match(subject).PSubs.Count.ShouldBe(1);
|
|
}
|
|
}
|
|
|
|
[Theory] // T:3004, T:3005
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RemoveWithWildcardsAsLiterals_CorrectRemoval(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
foreach (var subject in new[] { "foo.*-", "foo.>-" })
|
|
{
|
|
var sub = NewSub(subject);
|
|
s.Insert(sub);
|
|
s.Remove(NewSub("foo.bar")); // wrong subject, no-op
|
|
s.Count().ShouldBe(1u);
|
|
s.Remove(sub);
|
|
s.Count().ShouldBe(0u);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Race tests (T:3006-3010)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:3006, T:3007
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RaceOnRemove_NoCorruption(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var subs = new Subscription[100];
|
|
for (int i = 0; i < subs.Length; i++)
|
|
subs[i] = NewQSub("foo", "bar");
|
|
|
|
for (int iter = 0; iter < 2; iter++)
|
|
{
|
|
foreach (var sub in subs) s.Insert(sub);
|
|
if (iter == 1) s.Match("foo");
|
|
var r = s.Match("foo");
|
|
var task = Task.Run(() => { foreach (var sub in subs) s.Remove(sub); });
|
|
foreach (var qsub in r.QSubs)
|
|
for (int i = 0; i < qsub.Count; i++)
|
|
Encoding.ASCII.GetString(qsub[i].Queue!).ShouldBe("bar");
|
|
task.Wait();
|
|
}
|
|
}
|
|
|
|
[Theory] // T:3008, T:3009
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RaceOnInsert_NoCorruption(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var subs = new Subscription[100];
|
|
for (int i = 0; i < subs.Length; i++)
|
|
subs[i] = NewQSub("foo", "bar");
|
|
|
|
var task = Task.Run(() => { foreach (var sub in subs) s.Insert(sub); });
|
|
for (int i = 0; i < 1000; i++)
|
|
{
|
|
var r = s.Match("foo");
|
|
foreach (var qsub in r.QSubs)
|
|
foreach (var sub in qsub)
|
|
Encoding.ASCII.GetString(sub.Queue!).ShouldBe("bar");
|
|
}
|
|
task.Wait();
|
|
}
|
|
|
|
[Fact] // T:3010
|
|
public void RaceOnMatch_NoCorruption()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistNoCache();
|
|
s.Insert(NewQSub("foo.*", "workers"));
|
|
s.Insert(NewQSub("foo.bar", "workers"));
|
|
s.Insert(NewSub("foo.*"));
|
|
s.Insert(NewSub("foo.bar"));
|
|
|
|
Exception? error = null;
|
|
var tasks = Enumerable.Range(0, 2).Select(_ => Task.Run(() =>
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
var r = s.Match("foo.bar");
|
|
foreach (var sub in r.PSubs)
|
|
if (!Encoding.ASCII.GetString(sub.Subject).StartsWith("foo."))
|
|
Interlocked.CompareExchange(ref error, new Exception($"Wrong subject: {Encoding.ASCII.GetString(sub.Subject)}"), null);
|
|
foreach (var qsub in r.QSubs)
|
|
foreach (var sub in qsub)
|
|
if (Encoding.ASCII.GetString(sub.Queue!) != "workers")
|
|
Interlocked.CompareExchange(ref error, new Exception($"Wrong queue: {Encoding.ASCII.GetString(sub.Queue!)}"), null);
|
|
}
|
|
})).ToArray();
|
|
Task.WaitAll(tasks);
|
|
error.ShouldBeNull();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Remote Queue Subscriptions (T:3011, T:3012)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:3011, T:3012
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void RemoteQueueSubscriptions_WeightExpansion(bool cache)
|
|
{
|
|
var s = MakeSl(cache);
|
|
var s1 = NewQSub("foo", "bar");
|
|
var s2 = NewQSub("foo", "bar");
|
|
s.Insert(s1); s.Insert(s2);
|
|
|
|
var rs1 = NewRemoteQSub("foo", "bar", 10);
|
|
var rs2 = NewRemoteQSub("foo", "bar", 10);
|
|
s.Insert(rs1); s.Insert(rs2);
|
|
s.Count().ShouldBe(4u);
|
|
|
|
var r = s.Match("foo");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
r.QSubs.Count.ShouldBe(1);
|
|
r.QSubs[0].Count.ShouldBe(22); // 2 normal + 10 + 10 remote
|
|
|
|
s.Remove(s1); s.Remove(rs1);
|
|
s.Count().ShouldBe(2u);
|
|
r = s.Match("foo");
|
|
r.QSubs[0].Count.ShouldBe(11); // 1 normal + 10 remote
|
|
|
|
Interlocked.Exchange(ref rs2.Qw, 1);
|
|
s.UpdateRemoteQSub(rs2);
|
|
r = s.Match("foo");
|
|
r.QSubs[0].Count.ShouldBe(2); // 1 normal + 1 remote
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Shared Empty Result (T:3013)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3013
|
|
public void SharedEmptyResult_SameReference()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
var r1 = s.Match("foo");
|
|
var r2 = s.Match("bar");
|
|
r1.PSubs.Count.ShouldBe(0);
|
|
r2.QSubs.Count.ShouldBe(0);
|
|
ReferenceEquals(r1, r2).ShouldBeTrue();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// No-cache Stats (T:3014)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3014
|
|
public void NoCacheStats_CacheCountIsZero()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistNoCache();
|
|
s.Insert(NewSub("foo")); s.Insert(NewSub("bar")); s.Insert(NewSub("baz")); s.Insert(NewSub("foo.bar.baz"));
|
|
s.Match("a.b.c"); s.Match("bar");
|
|
s.Stats().NumCache.ShouldBe(0u);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// All (T:3015)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3015
|
|
public void All_CollectsAllSubscriptions()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistNoCache();
|
|
var subs = new[]
|
|
{
|
|
NewSub("foo.bar.baz"),
|
|
NewSub("foo"),
|
|
NewSub("baz"),
|
|
};
|
|
subs[0].Client!.Kind = ClientKind.Leaf;
|
|
foreach (var sub in subs) s.Insert(sub);
|
|
|
|
var output = new List<Subscription>();
|
|
s.All(output);
|
|
output.Count.ShouldBe(3);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// IsSubsetMatch (T:3016)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Theory] // T:3016
|
|
[InlineData("foo.bar", "foo.bar", true)]
|
|
[InlineData("foo.*", ">", true)]
|
|
[InlineData("foo.*", "*.*", true)]
|
|
[InlineData("foo.*", "foo.*", true)]
|
|
[InlineData("foo.*", "foo.bar", false)]
|
|
[InlineData("foo.>", ">", true)]
|
|
[InlineData("foo.>", "*.>", true)]
|
|
[InlineData("foo.>", "foo.>", true)]
|
|
[InlineData("foo.>", "foo.bar", false)]
|
|
[InlineData("foo..bar", "foo.*", false)]
|
|
[InlineData("foo.*", "foo..bar", false)]
|
|
public void IsSubsetMatch_CorrectResults(string subject, string test, bool expected)
|
|
{
|
|
SubscriptionIndex.SubjectIsSubsetMatch(subject, test).ShouldBe(expected);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ReverseMatch (T:3018, T:3019)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3018
|
|
public void ReverseMatch_FindsMatchingSubscriptions()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
var fooSub = NewSub("foo");
|
|
var barSub = NewSub("bar");
|
|
var fooBarSub = NewSub("foo.bar");
|
|
var fooBazSub = NewSub("foo.baz");
|
|
var fooBarBazSub = NewSub("foo.bar.baz");
|
|
s.Insert(fooSub); s.Insert(barSub); s.Insert(fooBarSub); s.Insert(fooBazSub); s.Insert(fooBarBazSub);
|
|
|
|
var r = s.ReverseMatch("foo");
|
|
r.PSubs.Count.ShouldBe(1); VerifyMember(r.PSubs, fooSub);
|
|
|
|
r = s.ReverseMatch("bar");
|
|
r.PSubs.Count.ShouldBe(1); VerifyMember(r.PSubs, barSub);
|
|
|
|
r = s.ReverseMatch("*");
|
|
r.PSubs.Count.ShouldBe(2);
|
|
VerifyMember(r.PSubs, fooSub); VerifyMember(r.PSubs, barSub);
|
|
|
|
r = s.ReverseMatch("baz");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
|
|
r = s.ReverseMatch("foo.*");
|
|
r.PSubs.Count.ShouldBe(2);
|
|
VerifyMember(r.PSubs, fooBarSub); VerifyMember(r.PSubs, fooBazSub);
|
|
|
|
r = s.ReverseMatch("*.*");
|
|
r.PSubs.Count.ShouldBe(2);
|
|
|
|
r = s.ReverseMatch("*.bar");
|
|
r.PSubs.Count.ShouldBe(1); VerifyMember(r.PSubs, fooBarSub);
|
|
|
|
r = s.ReverseMatch("bar.*");
|
|
r.PSubs.Count.ShouldBe(0);
|
|
|
|
r = s.ReverseMatch("foo.>");
|
|
r.PSubs.Count.ShouldBe(3);
|
|
|
|
r = s.ReverseMatch(">");
|
|
r.PSubs.Count.ShouldBe(5);
|
|
}
|
|
|
|
[Fact] // T:3019
|
|
public void ReverseMatchWider_MatchesWiderFilter()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
var sub = NewSub("uplink.*.*.>");
|
|
s.Insert(sub);
|
|
|
|
var r = s.ReverseMatch("uplink.1.*.*.>");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
VerifyMember(r.PSubs, sub);
|
|
|
|
r = s.ReverseMatch("uplink.1.2.3.>");
|
|
r.PSubs.Count.ShouldBe(1);
|
|
VerifyMember(r.PSubs, sub);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Match with empty tokens (T:3020)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3020
|
|
public void MatchWithEmptyTokens_ReturnsNoResults()
|
|
{
|
|
var sl = SubscriptionIndex.NewSublistWithCache();
|
|
sl.Insert(NewSub(">"));
|
|
sl.Insert(NewQSub(">", "queue"));
|
|
|
|
foreach (var subj in new[] { ".foo", "..foo", "foo..", "foo.", "foo..bar", "foo...bar" })
|
|
{
|
|
var r = sl.Match(subj);
|
|
r.PSubs.Count.ShouldBe(0);
|
|
r.QSubs.Count.ShouldBe(0);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// SubjectsCollide (T:3021)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3021
|
|
public void SubjectsCollide_CorrectResults()
|
|
{
|
|
SubscriptionIndex.SubjectsCollide("foo.*", "foo.*.bar.>").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectsCollide("foo.*.bar.>", "foo.*").ShouldBeFalse();
|
|
SubscriptionIndex.SubjectsCollide("foo.*", "foo.foo").ShouldBeTrue();
|
|
SubscriptionIndex.SubjectsCollide("foo.*", "*.foo").ShouldBeTrue();
|
|
SubscriptionIndex.SubjectsCollide("foo.bar.>", "*.bar.foo").ShouldBeTrue();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Cache hit rate (T:3022)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3022
|
|
public void AddCacheHitRate_CorrectAggregation()
|
|
{
|
|
var sl1 = SubscriptionIndex.NewSublistWithCache();
|
|
sl1.Insert(NewSub("foo"));
|
|
for (int i = 0; i < 4; i++) sl1.Match("foo");
|
|
var stats1 = sl1.Stats();
|
|
stats1.CacheHitRate.ShouldBe(0.75);
|
|
|
|
var sl2 = SubscriptionIndex.NewSublistWithCache();
|
|
sl2.Insert(NewSub("bar"));
|
|
for (int i = 0; i < 4; i++) sl2.Match("bar");
|
|
var stats2 = sl2.Stats();
|
|
stats2.CacheHitRate.ShouldBe(0.75);
|
|
|
|
var ts = new SublistStats();
|
|
ts.Add(stats1);
|
|
ts.Add(stats2);
|
|
ts.CacheHitRate.ShouldBe(0.75);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// HasInterest (T:3024)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3024
|
|
public void HasInterest_CorrectForLiteralsAndWildcards()
|
|
{
|
|
var sl = SubscriptionIndex.NewSublistWithCache();
|
|
var fooSub = NewSub("foo");
|
|
sl.Insert(fooSub);
|
|
|
|
sl.HasInterest("foo").ShouldBeTrue();
|
|
sl.HasInterest("bar").ShouldBeFalse();
|
|
|
|
sl.Remove(fooSub);
|
|
sl.HasInterest("foo").ShouldBeFalse();
|
|
|
|
// Partial wildcard.
|
|
var sub = NewSub("foo.*");
|
|
sl.Insert(sub);
|
|
sl.HasInterest("foo").ShouldBeFalse();
|
|
sl.HasInterest("foo.bar").ShouldBeTrue();
|
|
sl.HasInterest("foo.bar.baz").ShouldBeFalse();
|
|
sl.Remove(sub);
|
|
|
|
// Full wildcard.
|
|
sub = NewSub("foo.>");
|
|
sl.Insert(sub);
|
|
sl.HasInterest("foo").ShouldBeFalse();
|
|
sl.HasInterest("foo.bar").ShouldBeTrue();
|
|
sl.HasInterest("foo.bar.baz").ShouldBeTrue();
|
|
sl.Remove(sub);
|
|
|
|
// Queue subs.
|
|
var qsub = NewQSub("foo", "bar");
|
|
sl.Insert(qsub);
|
|
sl.HasInterest("foo").ShouldBeTrue();
|
|
sl.HasInterest("foo.bar").ShouldBeFalse();
|
|
sl.Remove(qsub);
|
|
sl.HasInterest("foo").ShouldBeFalse();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// HasInterest Overlapping (T:3025)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3025
|
|
public void HasInterestOverlapping_BothMatch()
|
|
{
|
|
var sl = SubscriptionIndex.NewSublistWithCache();
|
|
sl.Insert(NewSub("stream.A.child"));
|
|
sl.Insert(NewSub("stream.*"));
|
|
sl.HasInterest("stream.A.child").ShouldBeTrue();
|
|
sl.HasInterest("stream.A").ShouldBeTrue();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// NumInterest (T:3026)
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3026
|
|
public void NumInterest_CorrectCounts()
|
|
{
|
|
var sl = SubscriptionIndex.NewSublistWithCache();
|
|
sl.Insert(NewSub("foo"));
|
|
var (np, nq) = sl.NumInterest("foo");
|
|
np.ShouldBe(1);
|
|
nq.ShouldBe(0);
|
|
|
|
(np, nq) = sl.NumInterest("bar");
|
|
np.ShouldBe(0);
|
|
nq.ShouldBe(0);
|
|
|
|
// Add queue sub.
|
|
sl.Insert(NewQSub("foo", "q1"));
|
|
(np, nq) = sl.NumInterest("foo");
|
|
np.ShouldBe(1);
|
|
nq.ShouldBe(1);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Notifications (T:3017) — simplified from the large Go test
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact] // T:3017
|
|
public void RegisterInterestNotification_BasicFlow()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
var notifications = new List<bool>();
|
|
Action<bool> notify = b => notifications.Add(b);
|
|
|
|
// Wildcards should be rejected.
|
|
s.RegisterNotification("foo.*", notify).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
s.RegisterNotification(">", notify).ShouldBe(SubscriptionIndex.ErrInvalidSubject);
|
|
|
|
// Register for "foo" — no existing interest, should get false.
|
|
s.RegisterNotification("foo", notify).ShouldBeNull();
|
|
notifications.Count.ShouldBe(1);
|
|
notifications[0].ShouldBeFalse();
|
|
notifications.Clear();
|
|
|
|
// Insert interest — should get true.
|
|
var sub = NewSub("foo");
|
|
s.Insert(sub);
|
|
notifications.Count.ShouldBe(1);
|
|
notifications[0].ShouldBeTrue();
|
|
notifications.Clear();
|
|
|
|
// Insert a second — no notification (already have interest).
|
|
var sub2 = NewSub("foo");
|
|
s.Insert(sub2);
|
|
notifications.Count.ShouldBe(0);
|
|
|
|
// Remove all interest — should get false.
|
|
s.Remove(sub);
|
|
s.Remove(sub2);
|
|
notifications.Count.ShouldBe(1);
|
|
notifications[0].ShouldBeFalse();
|
|
notifications.Clear();
|
|
|
|
// Clear should return true.
|
|
s.ClearNotification("foo", notify).ShouldBeTrue();
|
|
|
|
// Clear again should return false (already cleared).
|
|
s.ClearNotification("foo", notify).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact] // T:3017 (queue notification portion)
|
|
public void RegisterQueueNotification_BasicFlow()
|
|
{
|
|
var s = SubscriptionIndex.NewSublistWithCache();
|
|
var notifications = new List<bool>();
|
|
Action<bool> notify = b => notifications.Add(b);
|
|
|
|
s.RegisterQueueNotification("foo.bar", "q1", notify).ShouldBeNull();
|
|
notifications.Count.ShouldBe(1);
|
|
notifications[0].ShouldBeFalse();
|
|
notifications.Clear();
|
|
|
|
var qsub = NewQSub("foo.bar", "q1");
|
|
s.Insert(qsub);
|
|
notifications.Count.ShouldBe(1);
|
|
notifications[0].ShouldBeTrue();
|
|
notifications.Clear();
|
|
|
|
s.Remove(qsub);
|
|
notifications.Count.ShouldBe(1);
|
|
notifications[0].ShouldBeFalse();
|
|
notifications.Clear();
|
|
|
|
s.ClearQueueNotification("foo.bar", "q1", notify).ShouldBeTrue();
|
|
}
|
|
}
|