From 1a3bfe0442baf7119ef57d534280cae44a989cb7 Mon Sep 17 00:00:00 2001 From: Grant Birchmeier Date: Sat, 1 Mar 2025 14:40:16 -0600 Subject: [PATCH] UTs to clarify some ResendRequest behavior also clarify a log message --- QuickFIXn/Session.cs | 20 +++++-- RELEASE_NOTES.md | 1 + UnitTests/SessionTest.cs | 123 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 9 deletions(-) diff --git a/QuickFIXn/Session.cs b/QuickFIXn/Session.cs index a8e271b54..47ae9710e 100755 --- a/QuickFIXn/Session.cs +++ b/QuickFIXn/Session.cs @@ -55,7 +55,6 @@ public bool IsNewSession } } - /// /// Session setting for heartbeat interval (in seconds) /// @@ -215,6 +214,11 @@ public TimeStampPrecision TimeStampPrecision /// public bool RequiresOrigSendingTime { get; set; } + /// + /// True if session is waiting for a ResendRequest content to finish. + /// + internal bool IsResendRequested => _state.ResendRequested(); + #endregion public Session( @@ -927,19 +931,22 @@ public bool Verify(Message msg, bool checkTooHigh = true, bool checkTooLow = tru return false; } - if (_state.ResendRequested()) + if (IsResendRequested) { ResendRange range = _state.GetResendRange(); if (msgSeqNum >= range.EndSeqNo) { - Log.OnEvent("ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.EndSeqNo + " has been satisfied."); + Log.OnEvent( + range.EndSeqNo == 0 + ? $"ResendRequest for messages FROM: {range.BeginSeqNo} TO: {range.EndSeqNo} has been satisfied." + : $"ResendRequest for messages FROM: {range.BeginSeqNo} TO: {range.EndSeqNo} has been started."); _state.SetResendRange(0, 0); } else if (msgSeqNum >= range.ChunkEndSeqNo) { Log.OnEvent("Chunked ResendRequest for messages FROM: " + range.BeginSeqNo + " TO: " + range.ChunkEndSeqNo + " has been satisfied."); SeqNumType newChunkEndSeqNo = Math.Min(range.EndSeqNo, range.ChunkEndSeqNo + MaxMessagesInResendRequest); - GenerateResendRequestRange(msg.Header.GetString(Fields.Tags.BeginString), range.ChunkEndSeqNo + 1, newChunkEndSeqNo); + GenerateResendRequestRange(msg.Header.GetString(Tags.BeginString), range.ChunkEndSeqNo + 1, newChunkEndSeqNo); range.ChunkEndSeqNo = newChunkEndSeqNo; } } @@ -1048,7 +1055,7 @@ protected void DoTargetTooHigh(Message msg, SeqNumType msgSeqNum) Log.OnEvent("MsgSeqNum too high, expecting " + _state.NextTargetMsgSeqNum + " but received " + msgSeqNum); _state.Queue(msgSeqNum, msg); - if (_state.ResendRequested()) + if (IsResendRequested) { ResendRange range = _state.GetResendRange(); @@ -1156,7 +1163,8 @@ protected bool GenerateResendRequestRange(string beginString, SeqNumType startSe return false; } - protected void GenerateResendRequest(string beginString, SeqNumType msgSeqNum) + // internal so it can be unit tested + internal void GenerateResendRequest(string beginString, SeqNumType msgSeqNum) { SeqNumType beginSeqNum = _state.NextTargetMsgSeqNum; SeqNumType endRangeSeqNum = msgSeqNum - 1; diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index dda4467e7..22b589d80 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -18,6 +18,7 @@ What's New **Non-breaking changes** * #939 - minor checkTooHigh/checkTooLow refactor in Session.cs (gbirchmeier) +* #nnn - clarify ResendRequest-related log message, add UT coverage for Session (gbirchmeier) ### v1.13.0 diff --git a/UnitTests/SessionTest.cs b/UnitTests/SessionTest.cs index 5bc9cf810..771a04b8b 100755 --- a/UnitTests/SessionTest.cs +++ b/UnitTests/SessionTest.cs @@ -170,7 +170,7 @@ public bool SENT_REJECT(int reason, int refTag) return refTagField.Value == refTag; } - public void SendNOSMessage() + public QuickFix.FIX42.NewOrderSingle CreateNOSMessage(SeqNumType n) { QuickFix.FIX42.NewOrderSingle order = new QuickFix.FIX42.NewOrderSingle( new QuickFix.Fields.ClOrdID("1"), @@ -182,9 +182,19 @@ public void SendNOSMessage() order.Header.SetField(new QuickFix.Fields.TargetCompID(_sessionId.SenderCompID)); order.Header.SetField(new QuickFix.Fields.SenderCompID(_sessionId.TargetCompID)); - order.Header.SetField(new QuickFix.Fields.MsgSeqNum(_seqNum++)); + order.Header.SetField(new QuickFix.Fields.SendingTime(DateTime.UtcNow)); + order.Header.SetField(new QuickFix.Fields.MsgSeqNum(n)); + return order; + } + + public void SendNOSMessage() + { + _session!.Next(CreateNOSMessage(_seqNum++).ConstructString()); + } - _session!.Next(order.ConstructString()); + public void SendNOSMessage(SeqNumType n) + { + _session!.Next(CreateNOSMessage(n).ConstructString()); } public void SendResendRequest(SeqNumType begin, SeqNumType end) @@ -824,5 +834,112 @@ public void TestResendRequestMsgSeqNumNotIgnoredWhenNoPersistance() SendNOSMessage(); Assert.That(!SENT_RESEND_REQUEST()); } + + [Test] + public void TestGenerateResendRequest() { + _session!.NextTargetMsgSeqNum = 100; + + // <= FIX41 + _session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX41, 125); + Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1)); + var rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue(); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100)); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(999999)); + + // >= FIX42 + _session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX42, 125); + Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1)); + rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue(); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100)); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(0)); + + // Max resend is set: request is greater than max resend + _session.MaxMessagesInResendRequest = 100; + _session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX42, 225); + Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1)); + rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue(); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100)); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(199)); + + // Max resend is set: request is lesser than max resend + _session.GenerateResendRequest(QuickFix.FixValues.BeginString.FIX42, 175); + Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1)); + rr = _responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue(); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.BeginSeqNo), Is.EqualTo(100)); + Assert.That(rr.GetInt(QuickFix.Fields.Tags.EndSeqNo), Is.EqualTo(174)); + } + + [Test] + public void TestBasicResendRequest_NoMax() { + // just a regular boring resend request scenario, when MaxMessagesInResendRequest is unset + + // Setup: Establish connection. Server sends Seq too high, causing client to ResendRequest. + Logon(); + SendNOSMessage(); + Assert.That(_session!.NextTargetMsgSeqNum, Is.EqualTo(3)); + + SendNOSMessage(6); + Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1)); + + var resendRequest = + (QuickFix.FIX42.ResendRequest)_responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue(); + Assert.That(resendRequest.BeginSeqNo.Value, Is.EqualTo(3)); + Assert.That(resendRequest.EndSeqNo.Value, Is.EqualTo(0)); // 0 is infinity, but actually we just want until 5 + + Assert.That(_session.IsResendRequested, Is.True); + + // Server sends 3 resent messages and 2 new messages + // (client already got seq=6) + foreach (SeqNumType i in new[] { 3, 4, 5, 7, 8 }) { + var msg = CreateNOSMessage(i); + if (i < 7) { + msg.Header.SetField(new QuickFix.Fields.PossDupFlag(true)); + msg.Header.SetField(new QuickFix.Fields.OrigSendingTime(DateTime.MinValue)); // (value doesn't matter for test) + _session.Next(msg.ConstructString()); + } + _session.Next(msg.ConstructString()); + + // When EndSeqNo is 0, we mark the ResendResend as 'finished' after the first resent message is received + Assert.That(_session.IsResendRequested, Is.False, $"seq num {i}"); + } + } + + [Test] + public void TestBasicResendRequest_WithMax() { + // just a regular boring resend request scenario, when MaxMessagesInResendRequest is configured + + // Setup: Establish connection. Server sends Seq too high, causing client to ResendRequest. + _session!.MaxMessagesInResendRequest = 100; + Logon(); + SendNOSMessage(); + Assert.That(_session!.NextTargetMsgSeqNum, Is.EqualTo(3)); + + SendNOSMessage(6); + Assert.That(_responder.GetCount(QuickFix.Fields.MsgType.RESEND_REQUEST), Is.EqualTo(1)); + + var resendRequest = + (QuickFix.FIX42.ResendRequest)_responder.MsgLookup[QuickFix.Fields.MsgType.RESEND_REQUEST].Dequeue(); + Assert.That(resendRequest.BeginSeqNo.Value, Is.EqualTo(3)); + Assert.That(resendRequest.EndSeqNo.Value, Is.EqualTo(5)); + + Assert.That(_session.IsResendRequested, Is.True); + + // Server sends 3 resent messages and 2 new messages + // (client already got seq=6) + foreach (SeqNumType i in new[] { 3, 4, 5, 7, 8 }) { + var msg = CreateNOSMessage(i); + if (i < 7) { + msg.Header.SetField(new QuickFix.Fields.PossDupFlag(true)); + msg.Header.SetField(new QuickFix.Fields.OrigSendingTime(DateTime.MinValue)); // (value doesn't matter for test) + _session.Next(msg.ConstructString()); + } + _session.Next(msg.ConstructString()); + + if (i < 5) + Assert.That(_session.IsResendRequested, Is.True, $"seq num {i}"); + else + Assert.That(_session.IsResendRequested, Is.False, $"seq num {i}"); + } + } }