From 211e0fd1d14ad6df6d37bed8aec36179b0a1b87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Thu, 9 Jul 2026 18:25:42 +0200 Subject: [PATCH 1/3] Add client-declared IsStateful capability to MTP Expose a client-declared statefulness capability so test frameworks can distinguish a stateful client (persists an addressable set of test nodes and their last-known state, e.g. an IDE test explorer) from a stateless client (streams updates, e.g. dotnet test). - Add experimental IClientCapabilities { IsStateful } surfaced via IClientInfo.Capabilities, mirroring the wire protocol's clientInfo/capabilities split. - Wire isStateful through the server-mode initialize handshake under capabilities.testing, backward-compatible (absent => stateless). - Default stateless in console host; build from client capabilities in server host. - Document the isStateful client capability in the protocol intro. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../001-protocol-intro.md | 7 +++++ .../Hosts/ConsoleTestHost.cs | 2 +- .../Hosts/ServerTestHost.RequestExecution.cs | 2 +- .../PublicAPI/PublicAPI.Unshipped.txt | 3 +++ .../JsonRpc/Json/Json.Deserializers.cs | 7 ++++- .../ServerMode/JsonRpc/JsonRpcMethods.cs | 1 + .../ServerMode/JsonRpc/RpcMessages.cs | 2 +- .../SerializerUtilities.Deserializers.cs | 3 ++- .../Services/ClientCapabilitiesService.cs | 6 +++++ .../Services/ClientInfoService.cs | 2 +- .../Services/IClientCapabilities.cs | 27 +++++++++++++++++++ .../Services/IClientInfo.cs | 5 ++++ .../ServerMode/v1.0.0/ClientCapabilities.cs | 5 +++- .../ObjectModel/ObjectModelConvertersTests.cs | 4 +-- .../ObjectModel/RunSettingsPatcherTests.cs | 12 ++++----- 15 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs create mode 100644 src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs diff --git a/docs/mstest-runner-protocol/001-protocol-intro.md b/docs/mstest-runner-protocol/001-protocol-intro.md index 79ac23cf63..04b4078afa 100644 --- a/docs/mstest-runner-protocol/001-protocol-intro.md +++ b/docs/mstest-runner-protocol/001-protocol-intro.md @@ -225,6 +225,13 @@ interface InitializeParams { // If true, the client supports the testing/testUpdates/attachments request. attachmentsSupport: true, + // If true, the client is stateful: it persists an addressable set of test nodes for the + // whole session and keeps each node in its last-known state until it is explicitly updated + // (for example, an IDE test explorer). If false or missing, the client is stateless: it + // consumes test updates as a stream and does not retain node state after the run + // (for example, `dotnet test`). Defaults to false. + isStateful: true, + // If true, the client support a port to which child processes // can connect to. // Note: The test runner is expected to ensure the synchronization of messages diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs index a00b732c24..1d941b4e08 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs @@ -24,7 +24,7 @@ internal sealed class ConsoleTestHost( : CommonHost(serviceProvider) { private static readonly ClientInfo ClientInfoHost = new("testingplatform-console", PlatformVersion.Version); - private static readonly IClientInfo ClientInfoService = new ClientInfoService("testingplatform-console", PlatformVersion.Version); + private static readonly IClientInfo ClientInfoService = new ClientInfoService("testingplatform-console", PlatformVersion.Version, new ClientCapabilitiesService(IsStateful: false)); private readonly ILogger _logger = serviceProvider.GetLoggerFactory().CreateLogger(); private readonly IClock _clock = serviceProvider.GetClock(); diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs index 98fc2d95b7..b2d20f8ad7 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs @@ -38,7 +38,7 @@ private async Task HandleRequestCoreAsync(RequestMessage message, RpcInv case (JsonRpcMethods.Initialize, InitializeRequestArgs args): _client = new(args.ClientInfo.Name, args.ClientInfo.Version); - _clientInfoService = new ClientInfoService(args.ClientInfo.Name, args.ClientInfo.Version); + _clientInfoService = new ClientInfoService(args.ClientInfo.Name, args.ClientInfo.Version, new ClientCapabilitiesService(args.Capabilities.IsStateful)); await _logger.LogDebugAsync($"Connection established with '{_client.Id}', protocol version {_client.Version}").ConfigureAwait(false); INamedFeatureCapability? namedFeatureCapability = ServiceProvider.GetTestFrameworkCapabilities().GetCapability(); diff --git a/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..01eaf70577 100644 --- a/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +[TPEXP]Microsoft.Testing.Platform.Services.IClientCapabilities +[TPEXP]Microsoft.Testing.Platform.Services.IClientCapabilities.IsStateful.get -> bool +[TPEXP]Microsoft.Testing.Platform.Services.IClientInfo.Capabilities.get -> Microsoft.Testing.Platform.Services.IClientCapabilities! diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs index 55e3f182d1..291a57a731 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs @@ -128,8 +128,13 @@ private static void RegisterDefaultDeserializers(Dictionary(testing, JsonRpcStrings.DebuggerProvider)); + DebuggerProvider: json.Bind(testing, JsonRpcStrings.DebuggerProvider), + IsStateful: isStateful); }); deserializers[typeof(InitializeResponseArgs)] = new JsonElementDeserializer( diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs index 579f21a080..12501e55f9 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs @@ -40,6 +40,7 @@ internal static class JsonRpcStrings public const string Capabilities = "capabilities"; public const string Testing = "testing"; public const string DebuggerProvider = "debuggerProvider"; + public const string IsStateful = "isStateful"; public const string SupportsDiscovery = "supportsDiscovery"; public const string MultiRequestSupport = "experimental_multiRequestSupport"; public const string VSTestProviderSupport = "vstestProvider"; diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs index 62d6e161a9..b6057a5112 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs @@ -90,7 +90,7 @@ internal sealed record InvalidRequestParamsArgs(int ErrorCode, string ErrorMessa internal sealed record ClientInfo(string Name, string Version); -internal sealed record ClientCapabilities(bool DebuggerProvider); +internal sealed record ClientCapabilities(bool DebuggerProvider, bool IsStateful); internal sealed record ServerInfo(string Name, string Version); diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs index 86bc97a4f8..3bcfb8d72a 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs @@ -110,8 +110,9 @@ private static void RegisterDeserializers() IDictionary capabilities = GetRequiredPropertyFromJson>(properties, JsonRpcStrings.Capabilities); IDictionary testingCapabilities = GetRequiredPropertyFromJson>(capabilities, JsonRpcStrings.Testing); bool debuggerProvider = GetRequiredPropertyFromJson(testingCapabilities, JsonRpcStrings.DebuggerProvider); + bool isStateful = GetOptionalPropertyFromJson(testingCapabilities, JsonRpcStrings.IsStateful) as bool? ?? false; - return new ClientCapabilities(debuggerProvider); + return new ClientCapabilities(debuggerProvider, isStateful); }); Deserializers[typeof(InitializeResponseArgs)] = new ObjectDeserializer(properties => diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs b/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs new file mode 100644 index 0000000000..1aa475541e --- /dev/null +++ b/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Testing.Platform.Services; + +internal sealed record ClientCapabilitiesService(bool IsStateful) : IClientCapabilities; diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs b/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs index 410c1e45e4..5a92a243e4 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs @@ -3,4 +3,4 @@ namespace Microsoft.Testing.Platform.Services; -internal sealed record ClientInfoService(string Id, string Version) : IClientInfo; +internal sealed record ClientInfoService(string Id, string Version, IClientCapabilities Capabilities) : IClientInfo; diff --git a/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs b/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs new file mode 100644 index 0000000000..855af610fc --- /dev/null +++ b/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Testing.Platform.Services; + +/// +/// Represents the capabilities declared by the client that is driving the test host. +/// +/// +/// Capabilities are opt-in: unless a client explicitly declares a capability, the platform assumes the +/// most conservative (default) behavior. This lets a test framework tailor its behavior to how the client +/// intends to consume the results without having to guess based on the environment or transport. +/// +[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")] +public interface IClientCapabilities +{ + /// + /// Gets a value indicating whether the client is stateful. + /// + /// + /// A stateful client persists an addressable set of test nodes for the whole session and keeps each node in + /// its last-known state until it is explicitly updated (for example, an IDE test explorer). A stateless client + /// consumes updates as a stream and does not retain node state after the run (for example, dotnet test). + /// The default is (stateless); a client opts into stateful behavior. + /// + bool IsStateful { get; } +} diff --git a/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs b/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs index 56361588a2..ff4f76a30e 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs @@ -18,4 +18,9 @@ public interface IClientInfo /// Gets the client version. /// string Version { get; } + + /// + /// Gets the capabilities declared by the client. + /// + IClientCapabilities Capabilities { get; } } diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs index 11073acf3d..c463045a3b 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs @@ -11,4 +11,7 @@ public sealed record ClientCapabilities( public sealed record ClientTestingCapabilities( [property: JsonProperty("debuggerProvider")] - bool DebuggerProvider); + bool DebuggerProvider, + + [property: JsonProperty("isStateful")] + bool IsStateful = false); diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs index e184daedb4..751730d74a 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.TrxReport.Abstractions; @@ -18,7 +18,7 @@ namespace Microsoft.Testing.Extensions.VSTestBridge.UnitTests.ObjectModel; [TestClass] public sealed class ObjectModelConvertersTests { - private static readonly IClientInfo ClientInfo = new ClientInfoService(WellKnownClients.VisualStudio, "1.0.0"); + private static readonly IClientInfo ClientInfo = new ClientInfoService(WellKnownClients.VisualStudio, "1.0.0", new ClientCapabilitiesService(IsStateful: false)); [TestMethod] [DataRow(true)] diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs index 262f7bc9ae..4eaa97a568 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.VSTestBridge.CommandLine; @@ -22,7 +22,7 @@ public void Patch_WhenNoRunSettingsProvided_CreateRunSettingsWithResultsDirector { _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); XDocument runSettingsDocument = RunSettingsPatcher.Patch(null, _configuration.Object, - new ClientInfoService(string.Empty, string.Empty), _commandLineOptions.Object); + new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); Assert.AreEqual( "/PlatformResultDirectory", runSettingsDocument.XPathSelectElement("RunSettings/RunConfiguration/ResultsDirectory")!.Value); @@ -41,7 +41,7 @@ public void Patch_WithRunSettingsProvidedButMissingResultsDirectory_AddsElement( _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), _commandLineOptions.Object); + XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); Assert.AreEqual( "/PlatformResultDirectory", runSettingsDocument.XPathSelectElement("RunSettings/RunConfiguration/ResultsDirectory")!.Value); @@ -62,7 +62,7 @@ public void Patch_WithRunSettingsContainingResultsDirectory_EntryIsNotOverridden """; _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), _commandLineOptions.Object); + XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); Assert.AreEqual( "/PlatformResultDirectoryFromFile", runSettingsDocument.XPathSelectElement("RunSettings/RunConfiguration/ResultsDirectory")!.Value); @@ -91,7 +91,7 @@ public void Patch_WhenRunSettingsExists_MergesParameters() }); _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), + XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); XElement[] testRunParameters = [.. runSettingsDocument.XPathSelectElements("RunSettings/TestRunParameters/Parameter")]; @@ -115,7 +115,7 @@ public void Patch_WhenRunSettingsDoesNotExist_AddParameters() }); _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(null, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), + XDocument runSettingsDocument = RunSettingsPatcher.Patch(null, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); XElement[] testRunParameters = [.. runSettingsDocument.XPathSelectElements("RunSettings/TestRunParameters/Parameter")]; From cabea29146653b2f3fcc02a8a8a9e20196877fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Thu, 9 Jul 2026 18:54:38 +0200 Subject: [PATCH 2/3] Fix build and address review: InternalAPI entries, UTF-8 BOM, isStateful tests - Declare new internal symbols (JsonRpcStrings.IsStateful, ClientCapabilities/ ClientCapabilitiesService/ClientInfoService members) in InternalAPI.Unshipped.txt to satisfy the newly-added InternalAPI tracking analyzer (RS0051) that broke the Linux CI build after merging main. - Restore the required UTF-8 BOM (charset=utf-8-bom) on the new and edited .cs files. - Add unit tests covering both deserializer paths (System.Text.Json and Jsonite) for isStateful: true and the absent (stateless) default. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../InternalAPI/InternalAPI.Unshipped.txt | 21 +++++ .../Services/ClientCapabilitiesService.cs | 2 +- .../Services/IClientCapabilities.cs | 2 +- .../ObjectModel/ObjectModelConvertersTests.cs | 2 +- .../ObjectModel/RunSettingsPatcherTests.cs | 2 +- .../ServerMode/JsonTests.cs | 85 +++++++++++++++++++ 6 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt index c84445ece8..e9af8be59f 100644 --- a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt @@ -1,3 +1,24 @@ #nullable enable +const Microsoft.Testing.Platform.ServerMode.JsonRpcStrings.IsStateful = "isStateful" -> string! +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.ClientCapabilities(bool DebuggerProvider, bool IsStateful) -> void +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.Deconstruct(out bool DebuggerProvider, out bool IsStateful) -> void +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.IsStateful.get -> bool +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.IsStateful.init -> void +Microsoft.Testing.Platform.Services.ClientCapabilitiesService +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.$() -> Microsoft.Testing.Platform.Services.ClientCapabilitiesService! +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.ClientCapabilitiesService(bool IsStateful) -> void +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.Deconstruct(out bool IsStateful) -> void +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.Equals(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? other) -> bool +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.IsStateful.get -> bool +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.IsStateful.init -> void +Microsoft.Testing.Platform.Services.ClientInfoService.Capabilities.get -> Microsoft.Testing.Platform.Services.IClientCapabilities! +Microsoft.Testing.Platform.Services.ClientInfoService.Capabilities.init -> void +Microsoft.Testing.Platform.Services.ClientInfoService.ClientInfoService(string! Id, string! Version, Microsoft.Testing.Platform.Services.IClientCapabilities! Capabilities) -> void +Microsoft.Testing.Platform.Services.ClientInfoService.Deconstruct(out string! Id, out string! Version, out Microsoft.Testing.Platform.Services.IClientCapabilities! Capabilities) -> void +override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.Equals(object? obj) -> bool +override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.GetHashCode() -> int +override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.ToString() -> string! +static Microsoft.Testing.Platform.Services.ClientCapabilitiesService.operator !=(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? left, Microsoft.Testing.Platform.Services.ClientCapabilitiesService? right) -> bool +static Microsoft.Testing.Platform.Services.ClientCapabilitiesService.operator ==(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? left, Microsoft.Testing.Platform.Services.ClientCapabilitiesService? right) -> bool static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.ReadFields(System.IO.Stream! stream, System.Func! tryReadField) -> void static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.WriteListPayload(System.IO.Stream! stream, ushort fieldId, T[]? list, System.Action! writeItem) -> void diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs b/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs index 1aa475541e..9222518814 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.Testing.Platform.Services; diff --git a/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs b/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs index 855af610fc..a90604471b 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.Testing.Platform.Services; diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs index 751730d74a..92596d053b 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.TrxReport.Abstractions; diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs index 4eaa97a568..b49abfd080 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.VSTestBridge.CommandLine; diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs index 9cb9e43d32..3b0452597d 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs @@ -202,6 +202,91 @@ public void GetProperties_WhenPropertiesDelegateIsNotSet_ThrowsInvalidOperationE Assert.Contains(nameof(TestJsonObjectSerializer), exception.Message); } + [TestMethod] + public void Deserialize_InitializeRequest_WithIsStatefulTrue_StjPath_SurfacesStatefulClient() + { + // Arrange + Json json = new(); + const string initializeParams = """ + { + "processId": 1, + "clientInfo": { "name": "client", "version": "1.0.0" }, + "capabilities": { "testing": { "debuggerProvider": true, "isStateful": true } } + } + """; + + // Act + InitializeRequestArgs args = json.Deserialize(initializeParams.AsMemory()); + + // Assert + Assert.IsTrue(args.Capabilities.IsStateful); + } + + [TestMethod] + public void Deserialize_InitializeRequest_WithoutIsStateful_StjPath_DefaultsToStateless() + { + // Arrange + Json json = new(); + const string initializeParams = """ + { + "processId": 1, + "clientInfo": { "name": "client", "version": "1.0.0" }, + "capabilities": { "testing": { "debuggerProvider": true } } + } + """; + + // Act + InitializeRequestArgs args = json.Deserialize(initializeParams.AsMemory()); + + // Assert + Assert.IsFalse(args.Capabilities.IsStateful); + } + + [TestMethod] + public void Deserialize_ClientCapabilities_WithIsStatefulTrue_JsonitePath_SurfacesStatefulClient() + { + // Arrange + Dictionary properties = new() + { + ["capabilities"] = new Dictionary + { + ["testing"] = new Dictionary + { + ["debuggerProvider"] = true, + ["isStateful"] = true, + }, + }, + }; + + // Act + ClientCapabilities capabilities = SerializerUtilities.Deserialize(properties); + + // Assert + Assert.IsTrue(capabilities.IsStateful); + } + + [TestMethod] + public void Deserialize_ClientCapabilities_WithoutIsStateful_JsonitePath_DefaultsToStateless() + { + // Arrange + Dictionary properties = new() + { + ["capabilities"] = new Dictionary + { + ["testing"] = new Dictionary + { + ["debuggerProvider"] = true, + }, + }, + }; + + // Act + ClientCapabilities capabilities = SerializerUtilities.Deserialize(properties); + + // Assert + Assert.IsFalse(capabilities.IsStateful); + } + private sealed class TestJsonObjectSerializer : JsonObjectSerializer; private sealed class Person From 87611b4bd9aa95a48ad502f5f52cf83660f6f4f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Thu, 9 Jul 2026 19:06:06 +0200 Subject: [PATCH 3/3] Add *REMOVED* InternalAPI markers for changed record signatures The ClientCapabilities and ClientInfoService record constructors (and their Deconstruct methods) changed signature, removing the old ones that are declared in InternalAPI.Shipped.txt. Declare them as *REMOVED* in InternalAPI.Unshipped.txt so the Public/Internal API analyzer (RS0017) does not fail the Arcade -warnaserror build. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../InternalAPI/InternalAPI.Unshipped.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt index e9af8be59f..7676911637 100644 --- a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt @@ -20,5 +20,9 @@ override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.GetHashCo override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.ToString() -> string! static Microsoft.Testing.Platform.Services.ClientCapabilitiesService.operator !=(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? left, Microsoft.Testing.Platform.Services.ClientCapabilitiesService? right) -> bool static Microsoft.Testing.Platform.Services.ClientCapabilitiesService.operator ==(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? left, Microsoft.Testing.Platform.Services.ClientCapabilitiesService? right) -> bool +*REMOVED*Microsoft.Testing.Platform.ServerMode.ClientCapabilities.ClientCapabilities(bool DebuggerProvider) -> void +*REMOVED*Microsoft.Testing.Platform.ServerMode.ClientCapabilities.Deconstruct(out bool DebuggerProvider) -> void +*REMOVED*Microsoft.Testing.Platform.Services.ClientInfoService.ClientInfoService(string! Id, string! Version) -> void +*REMOVED*Microsoft.Testing.Platform.Services.ClientInfoService.Deconstruct(out string! Id, out string! Version) -> void static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.ReadFields(System.IO.Stream! stream, System.Func! tryReadField) -> void static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.WriteListPayload(System.IO.Stream! stream, ushort fieldId, T[]? list, System.Action! writeItem) -> void