From 1563640f39967ed8f735666b23ceec4e96ce9e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 5 Jun 2026 16:00:19 +0200 Subject: [PATCH 1/3] Fix data collection channels to use negotiated protocol version instead of V1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The datacollector IPC channels (vstest.console↔datacollector and testhost↔datacollector) were always using V1 serialization because all SendMessage calls omitted the version parameter, which defaults to 1 in SocketCommunicationManager. This meant TestCase objects in DataCollectionTestStart/DataCollectionTestEnd messages used the slow V1 serializer instead of the modern serializer. Main channel (DataCollectionRequestSender / DataCollectionRequestHandler): - Sender sends BeforeTestRunStart at HighestSupportedVersion (V7) to advertise its capability. - Handler reads the request version, stores min(request, own highest) as _protocolVersion, and echoes that version in BeforeTestRunStartResult. - Sender reads the response version and adopts it as _protocolVersion for all subsequent messages (TestHostLaunched, AfterTestRunEnd). - Handler uses _protocolVersion for all outgoing messages (BeforeTestRunStartResult, AfterTestRunEndResult, DataCollectionMessage). Test-case event channel (DataCollectionTestCaseEventSender / DataCollectionTestCaseEventHandler): - Sender always sends at HighestSupportedVersion. - Handler echoes the incoming message version in DataCollectionTestEndResult. Backward compatibility is preserved: an old handler responds at V1 (no version param), which the sender reads and stores; an old sender sends at V1, which the handler stores. Fixes #15623 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DataCollectionRequestHandler.cs | 19 +++++++++++++++--- .../DataCollectionRequestSender.cs | 20 ++++++++++++++++--- .../DataCollectionTestCaseEventHandler.cs | 2 +- .../DataCollectionTestCaseEventSender.cs | 6 +++--- .../DataCollectionRequestHandlerTests.cs | 8 ++++---- .../DataCollectionRequestSenderTests.cs | 6 +++--- ...DataCollectionTestCaseEventHandlerTests.cs | 2 +- .../DataCollectionTestCaseEventSenderTests.cs | 6 +++--- 8 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs index 5dbd569619..6c47181773 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs @@ -48,6 +48,10 @@ internal class DataCollectionRequestHandler : IDataCollectionRequestHandler, IDi private readonly IFileHelper _fileHelper; private readonly IRequestData _requestData; + // The protocol version negotiated with the vstest.console sender. + // Set when BeforeTestRunStart is received; used for all responses on this channel. + private int _protocolVersion = 1; + private Task? _testCaseEventMonitorTask; /// @@ -212,7 +216,7 @@ public void ProcessRequests() /// public void SendDataCollectionMessage(DataCollectionMessageEventArgs args) { - _communicationManager.SendMessage(MessageType.DataCollectionMessage, args); + _communicationManager.SendMessage(MessageType.DataCollectionMessage, args, _protocolVersion); } /// @@ -296,6 +300,14 @@ private void AddExtensionAssemblies(BeforeTestRunStartPayload payload) private void HandleBeforeTestRunStart(Message message) { + // Negotiate the protocol version: adopt the highest version that both sides support. + // The sender transmits its highest supported version; we respond with the minimum of + // that and our own highest supported version so all subsequent messages use a mutually + // understood serialization format. + _protocolVersion = message.Version > 0 + ? Math.Min(message.Version, ProtocolVersioning.HighestSupportedVersion) + : 1; + // Initialize datacollectors and get environment variables. var payload = _dataSerializer.DeserializePayload(message); TPDebug.Assert(payload is not null, "payload is null"); @@ -355,7 +367,8 @@ private void HandleBeforeTestRunStart(Message message) _communicationManager.SendMessage( MessageType.BeforeTestRunStartResult, - new BeforeTestRunStartResult(envVariables, testCaseEventsPort)); + new BeforeTestRunStartResult(envVariables, testCaseEventsPort), + _protocolVersion); EqtTrace.Info("DataCollectionRequestHandler.ProcessRequests : DataCollection started."); } @@ -395,7 +408,7 @@ private void HandleAfterTestRunEnd(Message message) // As datacollector process exits itself on parent process(vstest.console) exits. _dataCollectionManager?.Dispose(); - _communicationManager.SendMessage(MessageType.AfterTestRunEndResult, afterTestRunEndResult); + _communicationManager.SendMessage(MessageType.AfterTestRunEndResult, afterTestRunEndResult, _protocolVersion); EqtTrace.Info("DataCollectionRequestHandler.ProcessRequests : Session End message received from server. Closing the connection."); Close(); diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs index a804beb0a0..64d2489b15 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs @@ -25,6 +25,10 @@ public sealed class DataCollectionRequestSender : IDataCollectionRequestSender private readonly ICommunicationManager _communicationManager; private readonly IDataSerializer _dataSerializer; + // The protocol version negotiated with the datacollector. + // Set after SendBeforeTestRunStartAndGetResult reads the response version. + private int _protocolVersion = 1; + /// /// Initializes a new instance of the class. /// @@ -94,7 +98,7 @@ public void Close() /// public void SendTestHostLaunched(TestHostLaunchedPayload testHostLaunchedPayload) { - _communicationManager.SendMessage(MessageType.TestHostLaunched, testHostLaunchedPayload); + _communicationManager.SendMessage(MessageType.TestHostLaunched, testHostLaunchedPayload, _protocolVersion); } /// @@ -112,7 +116,10 @@ public void SendTestHostLaunched(TestHostLaunchedPayload testHostLaunchedPayload IsTelemetryOptedIn = isTelemetryOptedIn }; - _communicationManager.SendMessage(MessageType.BeforeTestRunStart, payload); + // Send at the highest version this side supports; the datacollector echoes back the + // highest version it supports in the BeforeTestRunStartResult response, which then + // becomes the negotiated version for all subsequent messages on this channel. + _communicationManager.SendMessage(MessageType.BeforeTestRunStart, payload, ProtocolVersioning.HighestSupportedVersion); while (!isDataCollectionStarted) { @@ -133,6 +140,13 @@ public void SendTestHostLaunched(TestHostLaunchedPayload testHostLaunchedPayload else if (message.MessageType == MessageType.BeforeTestRunStartResult) { isDataCollectionStarted = true; + // Adopt the version the datacollector used in the response as the negotiated + // protocol version for all subsequent messages on this channel. + if (message.Version > 0) + { + _protocolVersion = message.Version; + } + result = _dataSerializer.DeserializePayload(message); } else if (message.MessageType == MessageType.TelemetryEventMessage) @@ -152,7 +166,7 @@ public void SendTestHostLaunched(TestHostLaunchedPayload testHostLaunchedPayload EqtTrace.Verbose("DataCollectionRequestSender.SendAfterTestRunStartAndGetResult: Send AfterTestRunEnd message with isCancelled: {0}", isCancelled); - _communicationManager.SendMessage(MessageType.AfterTestRunEnd, isCancelled); + _communicationManager.SendMessage(MessageType.AfterTestRunEnd, isCancelled, _protocolVersion); // Cycle through the messages that the datacollector sends. // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs index b8d0bf0241..2d852f0e48 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs @@ -122,7 +122,7 @@ public void ProcessRequests() attachmentSets = new Collection(); } - _communicationManager.SendMessage(MessageType.DataCollectionTestEndResult, attachmentSets); + _communicationManager.SendMessage(MessageType.DataCollectionTestEndResult, attachmentSets, message.Version); EqtTrace.Info("DataCollectionTestCaseEventHandler: Test case '{0} - {1}' completed", testCaseEndEventArgs?.TestCaseName, testCaseEndEventArgs?.TestCaseId); break; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs index 7c4b7c3985..2f8c3e1fc1 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs @@ -82,7 +82,7 @@ public void Close() /// public void SendTestCaseStart(TestCaseStartEventArgs e) { - _communicationManager.SendMessage(MessageType.DataCollectionTestStart, e); + _communicationManager.SendMessage(MessageType.DataCollectionTestStart, e, ProtocolVersioning.HighestSupportedVersion); var message = _communicationManager.ReceiveMessage(); if (message != null && message.MessageType != MessageType.DataCollectionTestStartAck) @@ -95,7 +95,7 @@ public void SendTestCaseStart(TestCaseStartEventArgs e) public Collection? SendTestCaseEnd(TestCaseEndEventArgs e) { var attachmentSets = new Collection(); - _communicationManager.SendMessage(MessageType.DataCollectionTestEnd, e); + _communicationManager.SendMessage(MessageType.DataCollectionTestEnd, e, ProtocolVersioning.HighestSupportedVersion); var message = _communicationManager.ReceiveMessage(); if (message != null && message.MessageType == MessageType.DataCollectionTestEndResult) @@ -109,6 +109,6 @@ public void SendTestCaseStart(TestCaseStartEventArgs e) /// public void SendTestSessionEnd(SessionEndEventArgs e) { - _communicationManager.SendMessage(MessageType.SessionEnd, e); + _communicationManager.SendMessage(MessageType.SessionEnd, e, ProtocolVersioning.HighestSupportedVersion); } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs index a55db45ed8..a56b2be3b5 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs @@ -127,13 +127,13 @@ public void SendDataCollectionMessageShouldSendMessageToCommunicationManager() _requestHandler.SendDataCollectionMessage(message); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionMessage, message), Times.Once); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionMessage, message, It.IsAny()), Times.Once); } [TestMethod] public void SendDataCollectionMessageShouldThrowExceptionIfThrownByCommunicationManager() { - _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionMessage, It.IsAny())).Throws(); + _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionMessage, It.IsAny(), It.IsAny())).Throws(); var message = new DataCollectionMessageEventArgs(TestMessageLevel.Error, "message"); Assert.ThrowsExactly(() => _requestHandler.SendDataCollectionMessage(message)); @@ -189,14 +189,14 @@ public void ProcessRequestsShouldProcessRequests() // Verify SessionStarted events _mockDataCollectionManager.Verify(x => x.SessionStarted(It.IsAny()), Times.Once); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStartResult, It.IsAny()), Times.Once); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStartResult, It.IsAny(), It.IsAny()), Times.Once); // Verify TestHostLaunched events _mockDataCollectionManager.Verify(x => x.TestHostLaunched(1234), Times.Once); // Verify AfterTestRun events. _mockDataCollectionManager.Verify(x => x.SessionEnded(It.IsAny()), Times.Once); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.AfterTestRunEndResult, It.IsAny()), Times.Once); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.AfterTestRunEndResult, It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs index d33db227b8..f477e67072 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestSenderTests.cs @@ -124,7 +124,7 @@ public void SendBeforeTestRunStartAndGetResultShouldSendBeforeTestRunStartMessag _mockDataSerializer.Setup(x => x.DeserializeMessage(rawMessage)).Returns(new Message() { MessageType = MessageType.BeforeTestRunStartResult }); _requestSender.SendBeforeTestRunStartAndGetResult(string.Empty, testSources, true, null); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStart, It.Is(p => p.SettingsXml == string.Empty && p.IsTelemetryOptedIn))); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStart, It.Is(p => p.SettingsXml == string.Empty && p.IsTelemetryOptedIn), ProtocolVersioning.HighestSupportedVersion)); } [TestMethod] @@ -140,7 +140,7 @@ public void SendBeforeTestRunStartAndGetResultShouldSendRawMessageIfTelemetry() _requestSender.SendBeforeTestRunStartAndGetResult(string.Empty, testSources, true, handlerMock.Object); handlerMock.Verify(x => x.HandleRawMessage(rawMessage1)); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStart, It.Is(p => p.SettingsXml == string.Empty && p.IsTelemetryOptedIn))); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStart, It.Is(p => p.SettingsXml == string.Empty && p.IsTelemetryOptedIn), ProtocolVersioning.HighestSupportedVersion)); } [TestMethod] @@ -154,6 +154,6 @@ public void SendBeforeTestRunStartAndGetResultShouldNotSendRawMessageIfTelemetry _mockDataSerializer.Setup(x => x.DeserializeMessage(rawMessage2)).Returns(new Message() { MessageType = MessageType.BeforeTestRunStartResult }); _requestSender.SendBeforeTestRunStartAndGetResult(string.Empty, testSources, true, null); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStart, It.Is(p => p.SettingsXml == string.Empty && p.IsTelemetryOptedIn))); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStart, It.Is(p => p.SettingsXml == string.Empty && p.IsTelemetryOptedIn), ProtocolVersioning.HighestSupportedVersion)); } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs index 809dd9a975..829d27d874 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs @@ -149,7 +149,7 @@ public void ProcessRequestsShouldProcessAfterTestCaseCompleteEvent() requestHandler.ProcessRequests(); _mockDataCollectionManager.Verify(x => x.TestCaseEnded(It.IsAny()), Times.Once); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestEndResult, It.IsAny>())); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestEndResult, It.IsAny>(), It.IsAny())); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs index 899ab22701..9faabc94d4 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs @@ -86,7 +86,7 @@ public void SendTestCaseStartShouldSendMessageThroughCommunicationManager() var testcaseStartEventArgs = new TestCaseStartEventArgs(_testCase); _dataCollectionTestCaseEventSender.SendTestCaseStart(testcaseStartEventArgs); - _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs), Times.Once); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs, ProtocolVersioning.HighestSupportedVersion), Times.Once); _mockCommunicationManager.Verify(x => x.ReceiveMessage(), Times.Once); } @@ -94,7 +94,7 @@ public void SendTestCaseStartShouldSendMessageThroughCommunicationManager() public void SendTestCaseStartShouldThrowExceptionIfThrownByCommunicationManager() { var testcaseStartEventArgs = new TestCaseStartEventArgs(_testCase); - _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs)).Throws(); + _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs, It.IsAny())).Throws(); Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.SendTestCaseStart(testcaseStartEventArgs)); } @@ -117,7 +117,7 @@ public void SendTestCaseCompletedShouldThrowExceptionIfThrownByCommunicationMana { var testCaseEndEventArgs = new TestCaseEndEventArgs(); - _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestEnd, It.IsAny())).Throws(); + _mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestEnd, It.IsAny(), It.IsAny())).Throws(); Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.SendTestCaseEnd(testCaseEndEventArgs)); } From 4b99347ab33d2a4f9a410ef30a0e86bdfa01d81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 5 Jun 2026 16:38:59 +0200 Subject: [PATCH 2/3] Address review: add sub-channel version negotiation and Math.Min test coverage - DataCollectionTestCaseEventHandler: echo Math.Min(message.Version, HighestSupportedVersion) in DataCollectionTestStartAck so the sender can detect the handler's maximum supported version - DataCollectionTestCaseEventSender: add _protocolVersion field (initialized to HighestSupportedVersion); adopt the negotiated version from the DataCollectionTestStartAck for all subsequent sends (SendTestCaseEnd, SendTestSessionEnd), giving the sub-channel the same Math.Min safety as the main DataCollection channel - DataCollectionRequestHandlerTests: add test that sends BeforeTestRunStart at version 4 and asserts both BeforeTestRunStartResult and AfterTestRunEndResult are sent at exactly version 4, verifying the Math.Min negotiation invariant - DataCollectionTestCaseEventHandlerTests: add test verifying DataCollectionTestStartAck echoes Math.Min(incomingVersion, HighestSupportedVersion) - DataCollectionTestCaseEventSenderTests: add tests verifying SendTestCaseEnd and SendTestSessionEnd use the version negotiated via the DataCollectionTestStartAck Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DataCollectionTestCaseEventHandler.cs | 2 +- .../DataCollectionTestCaseEventSender.cs | 17 +++++++++-- .../DataCollectionRequestHandlerTests.cs | 24 +++++++++++++++ ...DataCollectionTestCaseEventHandlerTests.cs | 28 ++++++++++++++++++ .../DataCollectionTestCaseEventSenderTests.cs | 29 +++++++++++++++++++ 5 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs index 2d852f0e48..08f02cbdb8 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs @@ -97,7 +97,7 @@ public void ProcessRequests() EqtTrace.Error($"DataCollectionTestCaseEventHandler.ProcessRequests: Error occurred during TestCaseStarted event handling: {ex}"); } - _communicationManager.SendMessage(MessageType.DataCollectionTestStartAck); + _communicationManager.SendMessage(MessageType.DataCollectionTestStartAck, string.Empty, Math.Min(message.Version, ProtocolVersioning.HighestSupportedVersion)); EqtTrace.Info("DataCollectionTestCaseEventHandler: Test case '{0} - {1}' started.", testCaseStartEventArgs?.TestCaseName, testCaseStartEventArgs?.TestCaseId); diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs index 2f8c3e1fc1..8ab0e7e7ff 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs @@ -18,6 +18,10 @@ public class DataCollectionTestCaseEventSender : IDataCollectionTestCaseEventSen private readonly ICommunicationManager _communicationManager; private readonly IDataSerializer _dataSerializer; + // Protocol version negotiated with the datacollector test case event handler. + // Updated from the DataCollectionTestStartAck echo after the first SendTestCaseStart. + private int _protocolVersion = ProtocolVersioning.HighestSupportedVersion; + /// /// Initializes a new instance of the class. /// @@ -82,20 +86,27 @@ public void Close() /// public void SendTestCaseStart(TestCaseStartEventArgs e) { - _communicationManager.SendMessage(MessageType.DataCollectionTestStart, e, ProtocolVersioning.HighestSupportedVersion); + _communicationManager.SendMessage(MessageType.DataCollectionTestStart, e, _protocolVersion); var message = _communicationManager.ReceiveMessage(); if (message != null && message.MessageType != MessageType.DataCollectionTestStartAck) { EqtTrace.Error("DataCollectionTestCaseEventSender.SendTestCaseStart : MessageType.DataCollectionTestStartAck not received."); } + + // Adopt the version echoed by the handler as the negotiated protocol version for all + // subsequent sends on this sub-channel. + if (message?.Version > 0) + { + _protocolVersion = message.Version; + } } /// public Collection? SendTestCaseEnd(TestCaseEndEventArgs e) { var attachmentSets = new Collection(); - _communicationManager.SendMessage(MessageType.DataCollectionTestEnd, e, ProtocolVersioning.HighestSupportedVersion); + _communicationManager.SendMessage(MessageType.DataCollectionTestEnd, e, _protocolVersion); var message = _communicationManager.ReceiveMessage(); if (message != null && message.MessageType == MessageType.DataCollectionTestEndResult) @@ -109,6 +120,6 @@ public void SendTestCaseStart(TestCaseStartEventArgs e) /// public void SendTestSessionEnd(SessionEndEventArgs e) { - _communicationManager.SendMessage(MessageType.SessionEnd, e, ProtocolVersioning.HighestSupportedVersion); + _communicationManager.SendMessage(MessageType.SessionEnd, e, _protocolVersion); } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs index a56b2be3b5..729c711d45 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionRequestHandlerTests.cs @@ -163,6 +163,30 @@ public void DisposeShouldCloseCommunicationChannel() _mockCommunicationManager.Verify(x => x.StopClient(), Times.Once); } + [TestMethod] + public void ProcessRequestsShouldNegotiateProtocolVersionToMinOfRequestAndHighest() + { + // Simulate a sender that supports only version 4 (less than HighestSupportedVersion = 7). + var beforeTestRunStartAtV4 = new Message() + { + MessageType = MessageType.BeforeTestRunStart, + Version = 4, + RawMessage = JsonDataSerializer.Instance.SerializePayload(MessageType.BeforeTestRunStart, new BeforeTestRunStartPayload { SettingsXml = "settingsxml", Sources = new List { "test1.dll" } }, 4) + }; + + _mockCommunicationManager.SetupSequence(x => x.ReceiveMessage()).Returns(beforeTestRunStartAtV4).Returns(_afterTestRunEnd); + _mockDataCollectionManager.Setup(x => x.SessionStarted(It.IsAny())).Returns(true); + var payload = new BeforeTestRunStartPayload { SettingsXml = "settingsxml", Sources = new List { "test1.dll" } }; + _mockDataSerializer.Setup(x => x.DeserializePayload(It.Is(y => y.MessageType == MessageType.BeforeTestRunStart))) + .Returns(payload); + + _requestHandler.ProcessRequests(); + + // Negotiated version = Math.Min(4, HighestSupportedVersion=7) = 4; all responses must use it. + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.BeforeTestRunStartResult, It.IsAny(), 4), Times.Once); + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.AfterTestRunEndResult, It.IsAny(), 4), Times.Once); + } + [TestMethod] public void ProcessRequestsShouldProcessRequests() { diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs index 829d27d874..f3b6dad1b1 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs @@ -98,6 +98,34 @@ public void CloseShouldNotThrowExceptionIfCommunicationManagerIsNull() _mockCommunicationManager.Verify(x => x.StopServer(), Times.Never); } + [TestMethod] + public void ProcessRequestsShouldEchoNegotiatedVersionInTestCaseStartAck() + { + // Simulate sender that supports only version 4. + var message = new Message + { + MessageType = MessageType.DataCollectionTestStart, + Version = 4, + RawMessage = JsonDataSerializer.Instance.SerializePayload(MessageType.DataCollectionTestStart, new TestCaseEndEventArgs(), 4), + }; + + var sessionEndMessage = new Message + { + MessageType = MessageType.SessionEnd, + Version = 7, + RawMessage = JsonDataSerializer.Instance.SerializePayload(MessageType.SessionEnd, "false", 7), + }; + _mockCommunicationManager.SetupSequence(x => x.ReceiveMessage()).Returns(message).Returns(sessionEndMessage); + + var requestHandler = new DataCollectionTestCaseEventHandler(_messageSink.Object, _mockCommunicationManager.Object, _mockDataCollectionManager.Object, _dataSerializer.Object); + _dataSerializer.Setup(x => x.DeserializePayload(message)).Returns(new TestCaseStartEventArgs()); + + requestHandler.ProcessRequests(); + + // Ack must echo Math.Min(4, HighestSupportedVersion) = 4 so the sender can adopt it. + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestStartAck, It.IsAny(), 4), Times.Once); + } + [TestMethod] public void ProcessRequestsShouldProcessBeforeTestCaseStartEvent() { diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs index 9faabc94d4..1378b2c939 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs @@ -121,4 +121,33 @@ public void SendTestCaseCompletedShouldThrowExceptionIfThrownByCommunicationMana Assert.ThrowsExactly(() => _dataCollectionTestCaseEventSender.SendTestCaseEnd(testCaseEndEventArgs)); } + + [TestMethod] + public void SendTestCaseEndShouldUseVersionNegotiatedFromTestCaseStartAck() + { + // Simulate a handler that echoes version 4 in the DataCollectionTestStartAck. + _mockCommunicationManager.SetupSequence(x => x.ReceiveMessage()) + .Returns(new Message() { MessageType = MessageType.DataCollectionTestStartAck, Version = 4 }) + .Returns(new Message() { MessageType = MessageType.DataCollectionTestEndResult, Version = 4, RawMessage = JsonDataSerializer.Instance.SerializePayload(MessageType.DataCollectionTestEndResult, new Collection(), 4) }); + + _dataCollectionTestCaseEventSender.SendTestCaseStart(new TestCaseStartEventArgs(_testCase)); + _dataCollectionTestCaseEventSender.SendTestCaseEnd(new TestCaseEndEventArgs()); + + // After negotiating version 4 via the ack, SendTestCaseEnd must use version 4. + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestEnd, It.IsAny(), 4), Times.Once); + } + + [TestMethod] + public void SendTestSessionEndShouldUseVersionNegotiatedFromTestCaseStartAck() + { + // Simulate a handler that echoes version 4 in the DataCollectionTestStartAck. + _mockCommunicationManager.Setup(x => x.ReceiveMessage()) + .Returns(new Message() { MessageType = MessageType.DataCollectionTestStartAck, Version = 4 }); + + _dataCollectionTestCaseEventSender.SendTestCaseStart(new TestCaseStartEventArgs(_testCase)); + _dataCollectionTestCaseEventSender.SendTestSessionEnd(new SessionEndEventArgs()); + + // After negotiating version 4 via the ack, SendTestSessionEnd must use version 4. + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.SessionEnd, It.IsAny(), 4), Times.Once); + } } From a0f630930caf333531d834d03247167163cf57a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 5 Jun 2026 17:06:42 +0200 Subject: [PATCH 3/3] Apply Math.Min guard to protocol version adoption and DataCollectionTestEndResult echo - DataCollectionTestCaseEventHandler: echo Math.Min(message.Version, HighestSupportedVersion) in DataCollectionTestEndResult (defense-in-depth, consistent with TestStartAck) - DataCollectionRequestSender: guard _protocolVersion = Math.Min(message.Version, HighestSupportedVersion) when adopting BeforeTestRunStartResult version - DataCollectionTestCaseEventSender: guard _protocolVersion = Math.Min(message.Version, HighestSupportedVersion) when adopting TestStartAck version - Add test ProcessRequestsShouldNegotiateVersionInTestCaseEndResult to verify Math.Min for TestEndResult Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DataCollectionRequestSender.cs | 3 +- .../DataCollectionTestCaseEventHandler.cs | 2 +- .../DataCollectionTestCaseEventSender.cs | 3 +- ...DataCollectionTestCaseEventHandlerTests.cs | 29 +++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs index 64d2489b15..5d26449203 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestSender.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.Globalization; using System.Net; @@ -144,7 +145,7 @@ public void SendTestHostLaunched(TestHostLaunchedPayload testHostLaunchedPayload // protocol version for all subsequent messages on this channel. if (message.Version > 0) { - _protocolVersion = message.Version; + _protocolVersion = Math.Min(message.Version, ProtocolVersioning.HighestSupportedVersion); } result = _dataSerializer.DeserializePayload(message); diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs index 08f02cbdb8..ee8c29c957 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs @@ -122,7 +122,7 @@ public void ProcessRequests() attachmentSets = new Collection(); } - _communicationManager.SendMessage(MessageType.DataCollectionTestEndResult, attachmentSets, message.Version); + _communicationManager.SendMessage(MessageType.DataCollectionTestEndResult, attachmentSets, Math.Min(message.Version, ProtocolVersioning.HighestSupportedVersion)); EqtTrace.Info("DataCollectionTestCaseEventHandler: Test case '{0} - {1}' completed", testCaseEndEventArgs?.TestCaseName, testCaseEndEventArgs?.TestCaseId); break; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs index 8ab0e7e7ff..4ee2207d8e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventSender.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.ObjectModel; using System.Net; @@ -98,7 +99,7 @@ public void SendTestCaseStart(TestCaseStartEventArgs e) // subsequent sends on this sub-channel. if (message?.Version > 0) { - _protocolVersion = message.Version; + _protocolVersion = Math.Min(message.Version, ProtocolVersioning.HighestSupportedVersion); } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs index f3b6dad1b1..005a7fb52e 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs @@ -152,6 +152,35 @@ public void ProcessRequestsShouldProcessBeforeTestCaseStartEvent() _mockDataCollectionManager.Verify(x => x.TestCaseStarted(It.IsAny()), Times.Once); } + [TestMethod] + public void ProcessRequestsShouldNegotiateVersionInTestCaseEndResult() + { + // Simulate sender that supports only version 4 (less than HighestSupportedVersion=7). + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + var message = new Message + { + MessageType = MessageType.DataCollectionTestEnd, + Version = 4, + RawMessage = JsonDataSerializer.Instance.SerializePayload(MessageType.DataCollectionTestEnd, new TestResultEventArgs(new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase)), 4), + }; + + var sessionEndMessage = new Message + { + MessageType = MessageType.SessionEnd, + Version = 7, + RawMessage = JsonDataSerializer.Instance.SerializePayload(MessageType.SessionEnd, "false", 7), + }; + _mockCommunicationManager.SetupSequence(x => x.ReceiveMessage()).Returns(message).Returns(sessionEndMessage); + + var requestHandler = new DataCollectionTestCaseEventHandler(_messageSink.Object, _mockCommunicationManager.Object, _mockDataCollectionManager.Object, _dataSerializer.Object); + _dataSerializer.Setup(x => x.DeserializePayload(message)).Returns(new TestCaseEndEventArgs()); + + requestHandler.ProcessRequests(); + + // Result must echo Math.Min(4, HighestSupportedVersion) = 4, not the higher handler version. + _mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestEndResult, It.IsAny>(), 4), Times.Once); + } + [TestMethod] public void ProcessRequestsShouldProcessAfterTestCaseCompleteEvent() {