Session 07 scope (5 features, 17 tests, ~1165 Go LOC): - Protocol/ParserTypes.cs: ParserState enum (79 states), PublishArgument, ParseContext - Protocol/IProtocolHandler.cs: handler interface decoupling parser from client - Protocol/ProtocolParser.cs: Parse(), ProtoSnippet(), OverMaxControlLineLimit(), ProcessPub/HeaderPub/RoutedMsgArgs/RoutedHeaderMsgArgs, ClonePubArg(), GetHeader() - tests/Protocol/ProtocolParserTests.cs: 17 tests via TestProtocolHandler stub Auth extras from session 06 (committed separately): - Auth/TpmKeyProvider.cs, Auth/CertificateIdentityProvider/, Auth/CertificateStore/ Internal utilities & data structures (session 06 overflow): - Internal/AccessTimeService.cs, ElasticPointer.cs, SystemMemory.cs, ProcessStatsProvider.cs - Internal/DataStructures/GenericSublist.cs, HashWheel.cs - Internal/DataStructures/SubjectTree.cs, SubjectTreeNode.cs, SubjectTreeParts.cs All 461 tests pass (460 unit + 1 integration). DB updated for features 2588-2592 and tests 2598-2614.
512 lines
18 KiB
C#
512 lines
18 KiB
C#
// Copyright 2025 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
using Shouldly;
|
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
|
|
|
namespace ZB.MOM.NatsNet.Server.Tests.Internal.DataStructures;
|
|
|
|
/// <summary>
|
|
/// Ports all 21 tests from Go's gsl/gsl_test.go.
|
|
/// </summary>
|
|
public sealed class GenericSublistTests
|
|
{
|
|
// -------------------------------------------------------------------------
|
|
// Helpers (mirror Go's require_* functions)
|
|
// -------------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Counts how many values the sublist matches for <paramref name="subject"/>
|
|
/// and asserts that count equals <paramref name="expected"/>.
|
|
/// Mirrors Go's <c>require_Matches</c>.
|
|
/// </summary>
|
|
private static void RequireMatches<T>(GenericSublist<T> s, string subject, int expected)
|
|
where T : notnull
|
|
{
|
|
var matches = 0;
|
|
s.Match(subject, _ => matches++);
|
|
matches.ShouldBe(expected);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistInit
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistInit()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Count.ShouldBe(0u);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistInsertCount
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistInsertCount()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("foo", EmptyStruct.Value);
|
|
s.Insert("bar", EmptyStruct.Value);
|
|
s.Insert("foo.bar", EmptyStruct.Value);
|
|
s.Count.ShouldBe(3u);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistSimple
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistSimple()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("foo", EmptyStruct.Value);
|
|
RequireMatches(s, "foo", 1);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistSimpleMultiTokens
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistSimpleMultiTokens()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("foo.bar.baz", EmptyStruct.Value);
|
|
RequireMatches(s, "foo.bar.baz", 1);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistPartialWildcard
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistPartialWildcard()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("a.b.c", EmptyStruct.Value);
|
|
s.Insert("a.*.c", EmptyStruct.Value);
|
|
RequireMatches(s, "a.b.c", 2);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistPartialWildcardAtEnd
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistPartialWildcardAtEnd()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("a.b.c", EmptyStruct.Value);
|
|
s.Insert("a.b.*", EmptyStruct.Value);
|
|
RequireMatches(s, "a.b.c", 2);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistFullWildcard
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistFullWildcard()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("a.b.c", EmptyStruct.Value);
|
|
s.Insert("a.>", EmptyStruct.Value);
|
|
RequireMatches(s, "a.b.c", 2);
|
|
RequireMatches(s, "a.>", 1);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistRemove
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistRemove()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
|
|
s.Insert("a.b.c.d", EmptyStruct.Value);
|
|
s.Count.ShouldBe(1u);
|
|
RequireMatches(s, "a.b.c.d", 1);
|
|
|
|
s.Remove("a.b.c.d", EmptyStruct.Value);
|
|
s.Count.ShouldBe(0u);
|
|
RequireMatches(s, "a.b.c.d", 0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistRemoveWildcard
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistRemoveWildcard()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
|
|
s.Insert("a.b.c.d", 11);
|
|
s.Insert("a.b.*.d", 22);
|
|
s.Insert("a.b.>", 33);
|
|
s.Count.ShouldBe(3u);
|
|
RequireMatches(s, "a.b.c.d", 3);
|
|
|
|
s.Remove("a.b.*.d", 22);
|
|
s.Count.ShouldBe(2u);
|
|
RequireMatches(s, "a.b.c.d", 2);
|
|
|
|
s.Remove("a.b.>", 33);
|
|
s.Count.ShouldBe(1u);
|
|
RequireMatches(s, "a.b.c.d", 1);
|
|
|
|
s.Remove("a.b.c.d", 11);
|
|
s.Count.ShouldBe(0u);
|
|
RequireMatches(s, "a.b.c.d", 0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistRemoveCleanup
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistRemoveCleanup()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.NumLevels().ShouldBe(0);
|
|
s.Insert("a.b.c.d.e.f", EmptyStruct.Value);
|
|
s.NumLevels().ShouldBe(6);
|
|
s.Remove("a.b.c.d.e.f", EmptyStruct.Value);
|
|
s.NumLevels().ShouldBe(0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistRemoveCleanupWildcards
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistRemoveCleanupWildcards()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.NumLevels().ShouldBe(0);
|
|
s.Insert("a.b.*.d.e.>", EmptyStruct.Value);
|
|
s.NumLevels().ShouldBe(6);
|
|
s.Remove("a.b.*.d.e.>", EmptyStruct.Value);
|
|
s.NumLevels().ShouldBe(0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistInvalidSubjectsInsert
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistInvalidSubjectsInsert()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
|
|
// Insert, or subscriptions, can have wildcards, but not empty tokens,
|
|
// and can not have a FWC that is not the terminal token.
|
|
Should.Throw<ArgumentException>(() => s.Insert(".foo", EmptyStruct.Value));
|
|
Should.Throw<ArgumentException>(() => s.Insert("foo.", EmptyStruct.Value));
|
|
Should.Throw<ArgumentException>(() => s.Insert("foo..bar", EmptyStruct.Value));
|
|
Should.Throw<ArgumentException>(() => s.Insert("foo.bar..baz", EmptyStruct.Value));
|
|
Should.Throw<ArgumentException>(() => s.Insert("foo.>.baz", EmptyStruct.Value));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistBadSubjectOnRemove
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistBadSubjectOnRemove()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
Should.Throw<ArgumentException>(() => s.Insert("a.b..d", EmptyStruct.Value));
|
|
Should.Throw<ArgumentException>(() => s.Remove("a.b..d", EmptyStruct.Value));
|
|
Should.Throw<ArgumentException>(() => s.Remove("a.>.b", EmptyStruct.Value));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistTwoTokenPubMatchSingleTokenSub
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistTwoTokenPubMatchSingleTokenSub()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert("foo", EmptyStruct.Value);
|
|
RequireMatches(s, "foo", 1);
|
|
RequireMatches(s, "foo.bar", 0);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistInsertWithWildcardsAsLiterals
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistInsertWithWildcardsAsLiterals()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
var subjects = new[] { "foo.*-", "foo.>-" };
|
|
for (var i = 0; i < subjects.Length; i++)
|
|
{
|
|
var subject = subjects[i];
|
|
s.Insert(subject, i);
|
|
RequireMatches(s, "foo.bar", 0);
|
|
RequireMatches(s, subject, 1);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistRemoveWithWildcardsAsLiterals
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistRemoveWithWildcardsAsLiterals()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
var subjects = new[] { "foo.*-", "foo.>-" };
|
|
for (var i = 0; i < subjects.Length; i++)
|
|
{
|
|
var subject = subjects[i];
|
|
s.Insert(subject, i);
|
|
RequireMatches(s, "foo.bar", 0);
|
|
RequireMatches(s, subject, 1);
|
|
Should.Throw<KeyNotFoundException>(() => s.Remove("foo.bar", i));
|
|
s.Count.ShouldBe(1u);
|
|
s.Remove(subject, i);
|
|
s.Count.ShouldBe(0u);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistMatchWithEmptyTokens
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistMatchWithEmptyTokens()
|
|
{
|
|
var s = GenericSublist<EmptyStruct>.NewSublist();
|
|
s.Insert(">", EmptyStruct.Value);
|
|
|
|
var subjects = new[]
|
|
{
|
|
".foo", "..foo", "foo..", "foo.", "foo..bar", "foo...bar"
|
|
};
|
|
|
|
foreach (var subject in subjects)
|
|
{
|
|
RequireMatches(s, subject, 0);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistHasInterest
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistHasInterest()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
s.Insert("foo", 11);
|
|
|
|
// Expect to find that "foo" matches but "bar" doesn't.
|
|
s.HasInterest("foo").ShouldBeTrue();
|
|
s.HasInterest("bar").ShouldBeFalse();
|
|
|
|
// Call Match on a subject we know there is no match.
|
|
RequireMatches(s, "bar", 0);
|
|
s.HasInterest("bar").ShouldBeFalse();
|
|
|
|
// Remove fooSub and check interest again.
|
|
s.Remove("foo", 11);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
|
|
// Try with some wildcards.
|
|
s.Insert("foo.*", 22);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
s.HasInterest("foo.bar").ShouldBeTrue();
|
|
s.HasInterest("foo.bar.baz").ShouldBeFalse();
|
|
|
|
// Remove sub, there should be no interest.
|
|
s.Remove("foo.*", 22);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
s.HasInterest("foo.bar").ShouldBeFalse();
|
|
s.HasInterest("foo.bar.baz").ShouldBeFalse();
|
|
|
|
s.Insert("foo.>", 33);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
s.HasInterest("foo.bar").ShouldBeTrue();
|
|
s.HasInterest("foo.bar.baz").ShouldBeTrue();
|
|
|
|
s.Remove("foo.>", 33);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
s.HasInterest("foo.bar").ShouldBeFalse();
|
|
s.HasInterest("foo.bar.baz").ShouldBeFalse();
|
|
|
|
s.Insert("*.>", 44);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
s.HasInterest("foo.bar").ShouldBeTrue();
|
|
s.HasInterest("foo.baz").ShouldBeTrue();
|
|
s.Remove("*.>", 44);
|
|
|
|
s.Insert("*.bar", 55);
|
|
s.HasInterest("foo").ShouldBeFalse();
|
|
s.HasInterest("foo.bar").ShouldBeTrue();
|
|
s.HasInterest("foo.baz").ShouldBeFalse();
|
|
s.Remove("*.bar", 55);
|
|
|
|
s.Insert("*", 66);
|
|
s.HasInterest("foo").ShouldBeTrue();
|
|
s.HasInterest("foo.bar").ShouldBeFalse();
|
|
s.Remove("*", 66);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistHasInterestOverlapping
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistHasInterestOverlapping()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
s.Insert("stream.A.child", 11);
|
|
s.Insert("stream.*", 11);
|
|
s.HasInterest("stream.A.child").ShouldBeTrue();
|
|
s.HasInterest("stream.A").ShouldBeTrue();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistHasInterestStartingInRace
|
|
// Tests that HasInterestStartingIn is safe to call concurrently with
|
|
// modifications to the sublist. Mirrors Go's goroutine test using Tasks.
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public async Task TestGenericSublistHasInterestStartingInRace()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
|
|
// Pre-populate with some patterns.
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
s.Insert("foo.bar.baz", i);
|
|
s.Insert("foo.*.baz", i + 10);
|
|
s.Insert("foo.>", i + 20);
|
|
}
|
|
|
|
const int iterations = 1000;
|
|
|
|
// Task 1: repeatedly call HasInterestStartingIn.
|
|
var task1 = Task.Run(() =>
|
|
{
|
|
for (var i = 0; i < iterations; i++)
|
|
{
|
|
s.HasInterestStartingIn("foo");
|
|
s.HasInterestStartingIn("foo.bar");
|
|
s.HasInterestStartingIn("foo.bar.baz");
|
|
s.HasInterestStartingIn("other.subject");
|
|
}
|
|
});
|
|
|
|
// Task 2: repeatedly modify the sublist.
|
|
var task2 = Task.Run(() =>
|
|
{
|
|
for (var i = 0; i < iterations; i++)
|
|
{
|
|
var val = 1000 + i;
|
|
var dynSubject = "test.subject." + (char)('a' + i % 26);
|
|
s.Insert(dynSubject, val);
|
|
s.Insert("foo.*.test", val);
|
|
// Remove may fail if not found (concurrent), so swallow KeyNotFoundException.
|
|
try { s.Remove(dynSubject, val); } catch (KeyNotFoundException) { }
|
|
try { s.Remove("foo.*.test", val); } catch (KeyNotFoundException) { }
|
|
}
|
|
});
|
|
|
|
// Task 3: also call HasInterest (which does lock).
|
|
var task3 = Task.Run(() =>
|
|
{
|
|
for (var i = 0; i < iterations; i++)
|
|
{
|
|
s.HasInterest("foo.bar.baz");
|
|
s.HasInterest("foo.something.baz");
|
|
}
|
|
});
|
|
|
|
await Task.WhenAll(task1, task2, task3);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TestGenericSublistNumInterest
|
|
// -------------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void TestGenericSublistNumInterest()
|
|
{
|
|
var s = GenericSublist<int>.NewSublist();
|
|
s.Insert("foo", 11);
|
|
|
|
void RequireNumInterest(string subj, int expected)
|
|
{
|
|
RequireMatches(s, subj, expected);
|
|
s.NumInterest(subj).ShouldBe(expected);
|
|
}
|
|
|
|
// Expect to find that "foo" matches but "bar" doesn't.
|
|
RequireNumInterest("foo", 1);
|
|
RequireNumInterest("bar", 0);
|
|
|
|
// Remove fooSub and check interest again.
|
|
s.Remove("foo", 11);
|
|
RequireNumInterest("foo", 0);
|
|
|
|
// Try with some wildcards.
|
|
s.Insert("foo.*", 22);
|
|
RequireNumInterest("foo", 0);
|
|
RequireNumInterest("foo.bar", 1);
|
|
RequireNumInterest("foo.bar.baz", 0);
|
|
|
|
// Remove sub, there should be no interest.
|
|
s.Remove("foo.*", 22);
|
|
RequireNumInterest("foo", 0);
|
|
RequireNumInterest("foo.bar", 0);
|
|
RequireNumInterest("foo.bar.baz", 0);
|
|
|
|
s.Insert("foo.>", 33);
|
|
RequireNumInterest("foo", 0);
|
|
RequireNumInterest("foo.bar", 1);
|
|
RequireNumInterest("foo.bar.baz", 1);
|
|
|
|
s.Remove("foo.>", 33);
|
|
RequireNumInterest("foo", 0);
|
|
RequireNumInterest("foo.bar", 0);
|
|
RequireNumInterest("foo.bar.baz", 0);
|
|
|
|
s.Insert("*.>", 44);
|
|
RequireNumInterest("foo", 0);
|
|
RequireNumInterest("foo.bar", 1);
|
|
RequireNumInterest("foo.bar.baz", 1);
|
|
s.Remove("*.>", 44);
|
|
|
|
s.Insert("*.bar", 55);
|
|
RequireNumInterest("foo", 0);
|
|
RequireNumInterest("foo.bar", 1);
|
|
RequireNumInterest("foo.bar.baz", 0);
|
|
s.Remove("*.bar", 55);
|
|
|
|
s.Insert("*", 66);
|
|
RequireNumInterest("foo", 1);
|
|
RequireNumInterest("foo.bar", 0);
|
|
s.Remove("*", 66);
|
|
}
|
|
}
|