From d5ab169bc556b8b1316ccd23d61bf5ec015ffa8c Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 1 Mar 2026 00:52:56 -0500 Subject: [PATCH] test(batch38-t5): add perf/race/mqtt/benchmark mapped tests --- .../ImplBacklog/ConcurrencyTests1.Batch38.cs | 50 ++++++++++++++++++ .../JetStreamBenchmarks.Impltests.cs | 28 ++++++++++ .../Server/MqttHandlerTests.cs | 30 +++++++++++ porting.db | Bin 6770688 -> 6770688 bytes 4 files changed, 108 insertions(+) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests1.Batch38.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamBenchmarks.Impltests.cs diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests1.Batch38.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests1.Batch38.cs new file mode 100644 index 0000000..e0173f3 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests1.Batch38.cs @@ -0,0 +1,50 @@ +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed partial class ConcurrencyTests1 +{ + [Fact] + public void NoRaceJetStreamDeleteStreamManyConsumers_ShouldSucceed() + { + var cluster = new JetStreamCluster(); + var account = "A"; + var stream = "S"; + + for (var i = 0; i < 250; i++) + { + var assignment = new ConsumerAssignment { Name = $"C{i}", Stream = stream }; + cluster.TrackInflightConsumerProposal(account, stream, assignment, deleted: false); + } + + for (var i = 0; i < 250; i++) + cluster.RemoveInflightConsumerProposal(account, stream, $"C{i}"); + + cluster.InflightConsumers.ContainsKey(account).ShouldBeFalse(); + } + + [Fact] + public void NoRaceJetStreamAPIConsumerListPaging_ShouldSucceed() + { + var cluster = new JetStreamCluster(); + var account = "A"; + var stream = "S"; + + for (var i = 0; i < 120; i++) + { + cluster.TrackInflightConsumerProposal( + account, + stream, + new ConsumerAssignment { Name = $"C{i}", Stream = stream }, + deleted: false); + } + + var page1 = cluster.InflightConsumers[account][stream].Keys.OrderBy(static k => k).Take(50).ToArray(); + var page2 = cluster.InflightConsumers[account][stream].Keys.OrderBy(static k => k).Skip(50).Take(50).ToArray(); + + page1.Length.ShouldBe(50); + page2.Length.ShouldBe(50); + page1.Intersect(page2).ShouldBeEmpty(); + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamBenchmarks.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamBenchmarks.Impltests.cs new file mode 100644 index 0000000..f498b13 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamBenchmarks.Impltests.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed class JetStreamBenchmarks +{ + [Fact] + public void BenchmarkJetStreamMetaSnapshot() + { + var started = Stopwatch.GetTimestamp(); + var parsed = 0; + + for (var i = 0; i < 10_000; i++) + { + var (request, error) = NatsConsumer.NextReqFromMsg("{\"batch\":1}"u8); + error.ShouldBeNull(); + request.ShouldNotBeNull(); + if (request!.Batch == 1) + parsed++; + } + + parsed.ShouldBe(10_000); + var elapsed = Stopwatch.GetElapsedTime(started); + elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(5)); + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Server/MqttHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Server/MqttHandlerTests.cs index 07d7099..310a336 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Server/MqttHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Server/MqttHandlerTests.cs @@ -16,4 +16,34 @@ public sealed class MqttHandlerTests err.ErrCode.ShouldBe(JsApiErrors.StreamReplicasNotSupported.ErrCode); err.Description.ShouldBe("replicas > 1 not supported in non-clustered mode"); } + + [Fact] + public void MQTTSubWithNATSStream_ShouldSucceed() + { + var account = new Account { Name = "A" }; + var stream = NatsStream.Create( + account, + new StreamConfig { Name = "MQTT", Subjects = ["mqtt.>"], Storage = StorageType.MemoryStorage }, + null, + null, + null, + null); + + stream.ShouldNotBeNull(); + + var (consumer, error) = stream!.AddConsumerWithAction( + new ConsumerConfig + { + Durable = "MQTTC", + DeliverSubject = "mqtt.deliver", + AckPolicy = AckPolicy.AckExplicit, + }, + oname: "MQTTC", + action: ConsumerAction.Create, + pedantic: false); + + error.ShouldBeNull(); + consumer.ShouldNotBeNull(); + consumer!.GetInfo().Stream.ShouldBe("MQTT"); + } } diff --git a/porting.db b/porting.db index 0044d5ab18eeb04d7fca04553fd0c681cf010e2d..6f92f2a8a6185f2e4af4ac9d266af3e2ec032b7e 100644 GIT binary patch delta 1549 zcmY+^drVVj6aetvCvClLzuvac@+@sB%BWE6up(2&+{RuGIz&d@td17Y43!w;lrWrE zE1TJtk?dqaT^Yo%#~&4N-{y$2SC+jTn3(AP8OfN5BvufYu(`z@V8bpqzdz2GbI;>T z?(K}727408&0A0=*h zy^oNU+&PuLF&qiEwS^l+n~uw3mm({_PUTpN(l9d9ANq)2Vs?NKCq)xvk)PwI_)#{(yLl_r$4Na5#)(f7uKf3ibub+#+uuHeqXf>7%`9_iVS|-o*Z(>n z<11AKOP!+_WiCZW`O1ZNKs`*pVH_|vOaequRQ{!mU4jA?{lo{6vzYM##0E$@R1`Cr zFuIDB=>gD^@u;K(TXo^Je{v~sL4y>LNv@+%@Sy!fh0Pj7!s zNIJ6*a1Y&Gq`3Blbj4v_ty3x3Y)z7cLFWlA=X$d#7H=y$`Lq_zl*+>tY<;y34J>qgg z=$!0;U*fVG+J8;Uhl#)S8g)sT&wr@ncq)+7avsR3DNpP(CF~ur(ozFp`iLJ1!

zoup$#R>M`V$q3=IuH;sql$d^8x46_!(WQe+dy-p4r8X0l+RO*rA@Hfe2^GJY(_ml1 ztcRZx=HxQmP4mota5PC-5N~$5p{U8SctHybL1(k&HMo?Y=7jiOiydw@TWl~jXY)Yu zRzYBW{%B8&1*M`iWJNZVjxtat%0k&F2iZ|Ba?qX@=f1AR@xQ#~G=U~9eX*$M^S@#_ zz8T&9XHYHLgzC^{RF9sebF&RnAu|brJy@gGwd-1SAzc9K?7=Elw*gMs zgLYO|3g_*?EN;0j9e%b4L#$4MvfQ9T$8v+-WsDxq*OOFMoP?5Wvcj+{`AZV(A!a5S z@U58?M8Ok8ThMdpd9;;+C-mZb%pX)33*|Ce{+iaUwQ98*6?D!J2UL%TB3k|mH_Cq} zhPXipZ8I!~$?=ep-Wd;du+Td|Z1X=BKiG4Kl&z^v?yadwj@1277$zA}!{rd#hPI;@ OP$LRc!{x}#fqwu7e_PW4 delta 1421 zcmY+=e^8Tk90zcopA6ZaefMl*3>+KVghU362quUw?1z#CQpsc{L~KAB;;y_TL(E4P z#Mxb4e69|R)V%WiV*>iUj8mka=TU)oc$I&|rFldhKg9FMy0g#VFaCHv_q^}`C*P;0+ zNrPi23CYrM7Pc-B41_{~I?=o=B`!;*Xrd!0iAT-s0U<)NonjsPs+c0O!d;<3s1odg zK@j-`eu^JrgM1pF$W3t9pgcl+Z2VpLEJE^FaU0A=NNv|SqM{Y&qO(jdu8<|RFKhPp zvJ_sHj`Af-YpVvxcT5h94v-46?8F{H0q-f22f_1rBGe_4654!$_yvxQ$)G=eN%DE- zO%R4iD>Dmk4UrLAeueDjSg~XA$Ix3}6Ey=(L2WXP|Can?W?lhdg5(NCqD$0)XM&uj zzfTZ@z*%D?NXci@SaCC?-X=+WbPT%THW^;c^Hni3AP`+6Z6>58v}L11hoC9O%5M6=;&dCJl+izCuA@D8j;t)ft#ib=uPF~xz^4EAgAS3VC$31 z!G2M)fX!!fg0D|Dz&CwzI@o?QNZgK2BUtP0cChx#>tJ}JITOexy9ti>%URId;nLC@ z{qnIwV4Cz^D4LQrP(3Y+FgT3`hHi=yotu__D2RS8fQfYd3V4vFcP*Bth3WdqIH>*8 znhCuh%0@UiD96IfZhhY3ML6r$AB6WN9bRZ1lvVJHTd#w_dFRsj^ov~m$oi$OyF{l3 zuI!_Dvfk_-Wd(v=rrM470Dp-$COM7~QT?!J!6Y z2h^%97Kk)B(_p5-Xojf;^U?<4?nWa@Kqi!k%qR&Zqm{^lQjitdP%5(1?ncLJ?O<+O$8YEz^zyvp@&mZ?snrF=_ngU)J?~V*;GD`F-A5B% zn0KO8C=I2fhtO)2f!3f*t6hUi2s` zK^xIy=yCJ}dJ=6yn^7rx3T;78qi0YVDo0yU1$q`ehn`2