Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add a source-only MTP server-mode client package - #10085

Merged
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package
Aug 7, 2026
Merged

Add a source-only MTP server-mode client package#10085
Amaury Levé (Evangelink) merged 29 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwndJakub Jareš (nohwnd) commented Jul 20, 2026

Copy link
Copy Markdown
Member

MTP ships only the server side of its server-mode JSON-RPC protocol today, so consumers that drive an MTP test app have had to maintain bespoke clients. This adds one canonical client, owned in testfx next to the protocol it implements, and ships it as source so vstest, VSUnitTesting, and C# Dev Kit can replace their copies without adding a runtime dependency.

What's here

  • A new src/Platform/Microsoft.Testing.Platform.ServerMode.Client.Sources project that links the server's protocol and serialization source and adds the client API, JSON-RPC connection, and process launcher.
  • A source-only Microsoft.Testing.Platform.ServerMode.Client.Sources package: no DLL, no runtime dependency, and all injected types are internal.
  • Package-private namespaces for linked protocol types, so consumers can reference Microsoft.Testing.Platform.dll without source/assembly type collisions.
  • Dependency-free, Native AOT-compatible serialization: Jsonite for .NET Framework, netstandard2.0, and net5.0-net7.0 consumers; in-box System.Text.Json for net8.0 and newer.
  • Synchronous and asynchronous launch APIs, cancellation-aware connection startup, event-safe lazy read-loop startup, and synchronized server-request handlers.
  • A curated set of down-level polyfills with explicit opt-out constants for consumers that already define common source polyfills.

Validation

  • Unit coverage exercises initialize, discover, run, filters, notifications, server requests, cancellation, malformed frames, disconnects, and both formatter paths on net462 and modern .NET.
  • A packed hostile-consumer compile gate covers net462, netstandard2.0, net5.0, net6.0, net7.0, and net8.0 with nullable analysis and warnings-as-errors while also referencing Microsoft.Testing.Platform.
  • A packed end-to-end consumer launches a real MTP app and verifies discovery and execution over the wire.
  • Package contract tests verify source-only layout, content-file manifests, namespace isolation, per-TFM formatter selection, curated polyfills, and build assets.
  • System.Text.Json and Jsonite preserve equivalent untyped numeric representations, including integers through decimal.MaxValue.

Scope

This PR is the testfx/package leg. Adoption in vstest, VSUnitTesting, and C# Dev Kit remains separate so each consumer can remove its bespoke implementation and adapt its repository-specific integration independently.

Jakub Jareš (nohwnd)and others added 3 commits July 15, 2026 15:23
MTP ships only the server side of its server-mode JSON-RPC protocol today, so
every consumer that drives an MTP app has to write its own client. There are
three of them: vstest's minimal Jsonite one, VSUnitTesting's mature
StreamJsonRpc one, and C# Dev Kit's copy of that. The plan is to own a single
client here in testfx and ship it as a source-only package so all three consume
the same code. This is the first step - the client and its tests, building and
green in-repo. Source-only contentFiles packaging comes later.
The client reuses the server's own serialization instead of taking a dependency,
so the wire format cannot drift: Jsonite on net462/netstandard, in-box
System.Text.Json on .NET. Both are dependency-free and AOT-safe.
The net8 leg needed two fixes in the shared STJ decoder, because the server only
ever decoded client-to-server requests and never exercised the receive path a
client needs:
- Register an object[] deserializer. The IDictionary deserializer already binds
object[] for array values, but nothing registered it, so any server-to-client
message carrying an array (attachments, node changes) killed the read loop.
- Keep raw params as an IDictionary for methods the server does not know. The
RpcMessage params switch only knew the five server request methods, so
client-received notifications dropped their params.
Both are behavior-preserving for the server - its serialization tests stay 56/56.
Tests run on both formatter paths, net8 (STJ) and net462 (Jsonite), 21/21 each.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drives a real generated MTP app through the source-only client's
MtpServerClient.Launch: initialize, discover, then run in two separate
launches, asserting the single action node comes back as discovered and
then passed. Runs the net462/net8.0/net10.0 child assets from the net11
host, so the net462 (Jsonite) server talking to the net8 (System.Text.Json)
client exercises both formatter paths over the real transport.
Also makes the client process launch cross-platform (apphost resolution on
Windows/Linux/macOS) and exposes the internals to the acceptance project via
an aliased project reference.
Convert Microsoft.Testing.Platform.ServerClient into the source-only package
Microsoft.Testing.Platform.ServerClient.Source. It ships the client plus the linked
server protocol and serialization source as contentFiles/cs/<tfm>/** (BuildAction=Compile),
so consumers compile it as internal types into their own assembly with no shipped DLL and
no runtime dependency. The pack target projects the final @(Compile) set into contentFiles,
so packed == compiled by construction, and the per-TFM System.Text.Json removal keeps
netstandard2.0 Jsonite-only (net462 / netstandard consumers never see the STJ path).
Add MtpServerClientSourcePackageTests, the anti-drift contract test: it inspects the produced
nupkg and asserts no compiled output, packed == compiled both ways, netstandard2.0 Jsonite-only
with net as a superset, the client API present in every target framework, and no polyfill or
generated-source leak. Name the readme PACKAGE.md so the shared Directory.Build.targets picks it up.
🤖
CopilotAI balanced review requested due to automatic review settings July 20, 2026 13:07

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a source-only MTP server-mode client package that reuses the platform’s protocol and serialization code.

Changes:

  • Adds client transport, process-launching, API, and packaging infrastructure.
  • Extends shared JSON-RPC deserialization for client notifications.
  • Adds unit, package-contract, and end-to-end acceptance tests.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 8 comments.

Show a summary per file
FileDescription
TestFx.slnxRegisters the new projects.
test/UnitTests/.../TestSetup.csRegisters client serializers for tests.
test/UnitTests/.../Program.csConfigures the test executable.
test/UnitTests/.../MtpServerClientTests.csTests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csprojConfigures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.csImplements the loopback fake server.
test/UnitTests/.../BannedSymbols.txtEnforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.csExercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csprojReferences the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.csValidates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.csAdds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.csSelects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.csSupplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.mdDocuments package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csprojDefines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.csAdds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.csLaunches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.csDefines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.csDefines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.csImplements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.csImplements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.csDefines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.csDefines client diagnostics abstractions.

Comment threadTestFx.slnx Outdated
Comment threadsrc/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
main added an ILogger (defaulting to NopLogger) to TcpMessageHandler for
low-noise transport diagnostics. The source client links that file, so a clean
build now needs ILogger, NopLogger, and the LoggingExtensions that define
LogDebugAsync. A stale obj hid this locally; the clean CI build failed with
CS0246. Link the three logging files. Client unit tests stay green on net8
(STJ) 21/21 and net462 (Jsonite) 21/21, and the source-package contract test
passes 5/5.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 13:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 21 comments.

Comments suppressed due to low confidence (7)

TestFx.slnx:61

  • The new platform project and its unit-test project are missing from both Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Those filters explicitly enumerate the other MTP projects/tests, so product-scoped and non-Windows builds will not compile or test this package. Add both entries to both filters.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Excluding generated global usings makes the packed sources depend on undocumented consumer imports. For example, MtpServerProcess.cs uses Process, StringBuilder, and RuntimeInformation without imports because this repo supplies them from Directory.Build.props:143,147,149; SDK implicit usings do not include all of these. An external consumer will fail to compile the content files unless it happens to define the same globals. Ship a package-owned imports source or add explicit imports, and validate the actual nupkg in a consumer with implicit usings disabled.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • Compiling these linked files as source does not make their declarations internal. This glob ships many public platform types (TestNode at Messages/TestNode.cs:9, state properties at TestNodeStateProperties.cs:9,56, and others) into every consumer assembly, contradicting the package contract and potentially triggering API-baseline failures or type-conflict warnings in consumers that reference MTP. Use an internalized client model/conditional accessibility rather than packing the public server model verbatim.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source requires newer syntax than C# 9: it uses file-scoped namespaces (C# 10), primary constructors such as PendingRequest(string method), and collection expressions such as ?? [] (C# 12). Either rewrite the package sources to the promised language level or state the actual C# 12 requirement.
- C# language version 9 or later.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:35

  • This idempotence check is not thread-safe, and the flag is set before the dictionaries are fully populated. Two concurrent Launch calls can let one thread observe true and create a System.Text.Json formatter from a partially registered serializer set; the dictionaries are also being read while mutated. Serialize the whole registration operation with a lock/one-time initialization and publish completion only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving notification params routes test-node payloads through the raw IDictionary decoder, whose number branch uses GetInt32(). The server serializes time.duration-ms as a double (Json.TestNodeSerializer.cs:170), so a normal fractional duration throws while decoding and fails the client's read loop. Decode generic JSON numbers as int/long/double (matching Jsonite) and add a fractional-duration notification test.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance test references the validation assembly, not Microsoft.Testing.Platform.ServerClient.Source, so it never exercises NuGet contentFiles selection or compilation into a consumer. The package-inspection test only checks zip structure; neither test would catch missing consumer imports or source-level type conflicts. Consume the packed package from a generated test project and run that output end to end.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

@github-actions

This comment has been minimized.

The ServerClient unit test app only registered AddMSTest, so it did not know
the --crashdump / --hangdump / --report-trx / --report-ctrf / --report-junit /
--report-azdo / --coverage options that test/Directory.Build.targets appends
when CI runs every unit test module through 'dotnet test --test-modules'. The
module rejected the unknown --hangdump option and exited 5, which the
orchestrator reports as 'zero tests ran' and fails the whole leg. Direct console
runs never passed --hangdump, so it only reproduced in the full CI run.
Register the same provider set every other testfx unit test app registers
(CrashDump, HangDump, Trx, JUnit, AzureDevOps, Ctrf, CodeCoverage, OpenTelemetry)
so the module accepts those options and runs its 21 tests. Verified by running
the built exe directly with the CI options on net8.0 and net462: both exit 0.
CopilotAI review requested due to automatic review settings July 20, 2026 14:32

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (8)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • This constant is only applied while this project builds; a contentFiles package does not propagate DefineConstants to consumers. The packed ObjectPool.cs therefore takes its #else namespace (Analyzer.Utilities.PooledObjects), while the packed .NET JSON engine references Microsoft.Testing.Platform.Helpers.ObjectPool, so a net8 consumer cannot compile the package. Propagate the constant through packaged build assets or remove the conditional dependency, and validate by compiling a package consumer.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • The packed sources rely on testfx's generated global usings, but those are deliberately omitted. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, and MtpServerProcess.cs relies on Process, StringBuilder, and runtime interop imports. Consumer-generated implicit usings do not include all of these, so otherwise valid consumers fail to compile. Add explicit/package-owned usings and compile an actual project from the nupkg.
 - Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform model with its original public accessibility: for example, Messages/TestNode.cs:9 declares public class TestNode, and the linked logging files expose public ILogger/LogLevel. That contradicts the PR/package contract that injected types are internal and can leak duplicate MTP public APIs (and conflict warnings) into consumer assemblies. Internalize/curate the linked contract or explicitly revise the package design and documentation.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • The new raw-property-bag path cannot decode all valid server numbers: the generic dictionary/array deserializers call JsonElement.GetInt32(), but real test nodes serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double. A fractional duration throws while decoding testing/testUpdates/tests, causing the client read loop and pending run to fail. Preserve int/long/double values as appropriate and cover a non-integral duration.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the serializer dictionaries are populated. Two concurrent first calls (for example parallel Launch calls in a consumer) can either mutate Dictionary concurrently or let one formatter snapshot a partially registered set. Serialize the entire registration and set the completed flag only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

TestFx.slnx:61

  • The new platform product and unit-test projects are only added to TestFx.slnx; both are absent from Microsoft.Testing.Platform.slnf and NonWindowsTests.slnf. Product-scoped and non-Windows builds will therefore skip building/packing the client and running its tests. Add both project paths to both filters, following the existing platform project convention.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:31

  • The shipped sources require C# 12 (they use primary constructors and collection expressions), not C# 9. The linked required members also need RequiredMemberAttribute and CompilerFeatureRequiredAttribute polyfills on older targets. Update the consumer requirements so following this documentation produces a compilable project.
- C# language version 9 or later.
- On `net462` / `netstandard2.0`: the usual polyfills (nullable attributes, `IsExternalInit`,
index/range, `System.HashCode`, `ValueTask`) and framework references (`System.Memory`,
`System.Threading.Tasks.Extensions`). This package intentionally does **not** ship polyfills, to

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This acceptance path consumes the validation DLL via ProjectReference, not the source-only nupkg, so it inherits testfx's constants/global usings and never verifies that contentFiles compile in a consumer. The archive-inspection tests cannot catch consumer compilation failures. Generate a small client asset with a PackageReference to the packed Shipping package and drive the server through that compiled asset.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

On non-Windows, eng/Build.props builds and packs NonWindowsTests.slnf, not
the full TestFx.slnx. The source-only package project was missing from that
filter, so on Linux/macOS it only built transitively (as a dependency of the
acceptance tests) and never packed. The acceptance tests then failed with
'Could not find Microsoft.Testing.Platform.ServerClient.Source.*.nupkg'.
Add the package project and its unit tests to the filter. The unit tests
already restrict net462 to Windows, so on non-Windows they build and run the
net8.0 (System.Text.Json) path only.
🤖
CopilotAI review requested due to automatic review settings July 20, 2026 14:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (22)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:193

  • The packed source is not self-contained. Files such as MtpJsonRpcConnection.cs and MtpServerProcess.cs use ConcurrentDictionary, Process, StringBuilder, RuntimeInformation, and other types without file-level imports; they compile here only because Directory.Build.props generates repository-wide global usings. This target deliberately excludes generated sources, so a normal external consumer will receive none of those imports and fail compilation. Please add explicit/shipped imports and validate the nupkg in a clean consumer project.
 <_MtpClientPackSource Include="@(Compile)"
Condition="'%(Compile.MtpClientDoNotPack)' != 'true' and
!$([System.String]::new('%(Compile.FullPath)').StartsWith('$(_MtpClientIntermediateFullPath)', System.StringComparison.OrdinalIgnoreCase))" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:126

  • This glob ships the platform message declarations with their original accessibility. For example, Messages/TestNode.cs:9 and TestNodeUpdateMessage.cs:14 are public, so NuGet does not compile the injected source “as internal”; it adds duplicate public MTP types to every consumer and can shadow types from Microsoft.Testing.Platform. Please make the source-package copies internal (or avoid shipping duplicate model declarations) before publishing.
 <!-- Data model (TestNode + property model). Glob Messages\ then remove the server-only message bus. -->
<ItemGroup>
<Compile Include="$(MTPDir)Messages\*.cs" Link="Linked\Messages\%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • Registration is not thread-safe, and the flag is published before the dictionaries are fully populated. Two concurrent Launch calls can let one thread create a formatter from a partial serializer snapshot while the other mutates the shared Dictionary instances. Serialize initialization under a lock and set the completed flag only after every registration has finished.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:130

  • Preserving unknown notification params now routes telemetry and test-node property bags through the generic decoder, but that decoder uses GetInt32() for every JSON number (including the new array path). The server serializer explicitly emits long, float, double, and decimal; a duration or non-integral telemetry metric therefore throws and terminates the client's read loop. Decode the supported numeric shapes without narrowing, and cover a double/long notification.
 _ => value.ValueKind == JsonValueKind.Object
? json.Bind<IDictionary<string, object?>>(value)
: null,

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped client already uses C# 12 syntax, including primary constructors (DelegateMtpClientLogger and PendingRequest) and collection expressions. A consumer compiling with C# 9 cannot parse the package sources, so this requirement is incorrect.
- C# language version 9 or later.

TestFx.slnx:61

  • The new platform product and its unit tests are added to the full and non-Windows solutions, but both are absent from Microsoft.Testing.Platform.slnf (currently lines 8-35). Product-scoped platform builds therefore skip this package and its tests. Add both project paths to that filter as well.
 <Project Path="src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj" />

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj:51

  • This ProjectReference makes the end-to-end test run against the built DLL under testfx's global usings, polyfills, and IS_CORE_MTP; it never restores or compiles Microsoft.Testing.Platform.ServerClient.Source. Consequently the test named ViaSourcePackageClient cannot catch source-package consumer failures. Build a clean generated asset with a PackageReference to the packed nupkg and drive that client instead.
 <!-- The MTP server-mode client under test (P4). Referenced through the `serverclient` extern alias so
its internal TestNode/TestNodeUpdateMessage (same namespaces as the platform's public types) stay
out of the global namespace and never clash in the non-aliased files of this assembly. -->
<ProjectReference Include="$(RepoRoot)src\Platform\Microsoft.Testing.Platform.ServerClient\Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is saved without the required UTF-8 BOM. .editorconfig:65-68 sets charset = utf-8-bom for all C# files; please resave it with the configured encoding.
// Copyright (c) Microsoft Corporation. All rights reserved.

The source-only ServerClient package embeds the server's Jsonite under a
top-level `namespace Jsonite`. vstest already has its own internal top-level
`namespace Jsonite`, so on net462/netstandard2.0 both copies compile into
CrossPlatEngine and collide (CS0436), failing vstest's warnings-as-errors build.
Move it under `Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite`
(matches the folder). Pure namespace move, no wire-format or behavior change:
the formatter Id stays "Jsonite" and the JSON output is identical. Server and
client compile from the same files, so the rename is unconditional.
Validated: platform + client unit tests (net462 Jsonite + net8 STJ 21/21 each,
platform 1371/1393), the packed==compiled contract test (5/5), and the
real-app acceptance test (3/3) all green.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 08:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (20)

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:33

  • DefineConstants only affects this validation project; it is not propagated with contentFiles. A package consumer therefore compiles ObjectPool.cs without IS_CORE_MTP, placing ObjectPool<T> in Analyzer.Utilities.PooledObjects (Helpers/ObjectPool.cs:21-25), while the packed Json/Json.cs imports Microsoft.Testing.Platform.Helpers and instantiates that type. The net8 source package will not compile. Propagate the symbol through package build assets or remove the conditional namespace dependency from the shipped source.
 <DefineConstants>$(DefineConstants);IS_CORE_MTP</DefineConstants>

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:182

  • Skipping generated global usings makes the packed source depend on testfx's Directory.Build.props, which consumers do not receive. For example, MtpJsonRpcConnection.cs uses ConcurrentDictionary without importing System.Collections.Concurrent, MtpServerProcess.cs uses Process/StringBuilder without their namespaces, and the non-.NET path relies on the project-only Polyfills using. The nupkg therefore fails to compile in a normal consumer. Add explicit imports to shipped files (or a compatible packaged imports mechanism).
 Skipped:
- Polyfills (MtpClientDoNotPack=true): consumers already provide their own.
- Anything generated into the intermediate obj dir (GlobalUsings.g.cs, *.AssemblyInfo.cs,
*.AssemblyAttributes.cs, …): each consumer generates its own.

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:74

  • This generic decoder rejects valid server numbers that are not Int32. In particular, test-node serialization emits TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so an ordinary timed test update makes GetInt32() throw and terminates the client read loop. The dictionary-number branch above has the same limitation. Decode int, long, and floating-point JSON numbers in both branches.
 case JsonValueKind.Number:
items.Add(element.GetInt32());

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:37

  • The idempotence guard is not thread-safe. If two clients launch concurrently, one thread can observe true while the first is still mutating the shared serializer dictionaries, then snapshot an incomplete set in CreateFormatter; requests later fail due to missing serializers. Synchronize the entire registration and publish the completed state only after all entries are installed.
 if (s_clientSerializersRegistered)
{
return;
}
s_clientSerializersRegistered = true;

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:28

  • The shipped source uses C# 12 features, including collection expressions ([]) and primary constructors, so it cannot compile with the documented C# 9 minimum. Either rewrite the injected source to C# 9 syntax or state the actual minimum.
- C# language version 9 or later.

test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/TestSetup.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs:1

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires utf-8-bom for all C# files. Resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

…e MTP client
MtpTestNodeUpdate now decodes standardOutput, standardError, and the location.file/line-start/line-end wire keys into StandardOutput, StandardError, FilePath, LineStart, and LineEnd, so consumers stop reaching into the raw Node bag for the common fields. Line numbers arrive as JSON numbers, so a small coercion handles whichever numeric type each formatter boxes them as.
Also documents the discover/run ordering guarantee: once the returned task completes every TestNodesUpdated handler has already run, so consumers do not need a settle delay or completion sentinel. This replaces the old fixed wait the vstest client used.
Tested on both formatter paths (net8 System.Text.Json, net462 Jsonite): unit 22/22 each, contract 5/5, acceptance 3/3.
🤖
CopilotAI review requested due to automatic review settings July 21, 2026 09:10

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md:29

  • This understates the compiler requirement. The package ships Polyfills/OperatingSystem.cs, whose active net462/netstandard2.0 branch uses a C# 14 extension block (extension(OperatingSystem) at line 15). With a C# 12 or 13 compiler, the packaged target sets LangVersion=latest but the injected source still fails to parse. Either avoid that C# 14 syntax in shipped source or document C# 14 as the minimum.
- C# language version 12 or later (the shipped source uses collection expressions and other C# 12
features).

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:323

  • The self-wait guard is unreliable for this async loop. Task.Run(Func<Task>) stores an unwrapped proxy task, while Task.CurrentId inside an async continuation is not guaranteed to equal that proxy's ID (and is commonly null). If an event or server-request handler calls Dispose, this can therefore wait five seconds on the read loop that is currently executing the handler. Track an explicit read-loop/dispatch context or avoid synchronously waiting when disposal originates from a callback.
 Task? readLoop = _readLoop;
if (readLoop is not null && Task.CurrentId != readLoop.Id)
{
try
{
readLoop.Wait(ReadLoopShutdownTimeout);

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for all valid JSON integers. After ulong, Jsonite tries decimal (Jsonite/JsonReader.cs:519-523), whereas this path converts directly to double; an integer such as decimal.MaxValue is therefore preserved on the Jsonite TFM but rounded on the System.Text.Json TFM. Untyped telemetry/property-bag values can consequently differ or lose precision. Preserve decimal for integer-form tokens beyond ulong, while retaining double for fractional/exponent tokens.
 if (element.TryGetUInt64(out ulong ulongValue))
{
return ulongValue;
}
return element.GetDouble();

- AsInt: test double integrality with the constant pattern d % 1d is 0d
instead of d == Math.Floor(d), so the code-scanning float-equality rule
does not fire (behaviorally identical).
- MtpJsonRpcConnection.Dispose: guard the read-loop self-wait with an
AsyncLocal<bool> flow marker instead of Task.CurrentId. ReadLoopAsync is
async, so after its first await Task.CurrentId no longer matches the loop's
task id and a handler-triggered Dispose would self-wait for the full 5s
shutdown timeout. Adds a regression test.
- MtpServerProcess: cap the retained standard-error buffer at 64 KB with a
front-trim so a chatty/long-lived server cannot grow it without bound; the
tail (most relevant near a crash) is kept.
- PACKAGE.md: correct the C# language-version note (build targets default
LangVersion=latest; a pinned version needs C# 14 on net462/netstandard2.0).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 12:45

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • The summary says false makes the client perform one operation and then exit, but the implementation only sends this value during initialization; it never auto-exits after discover/run. The remarks below describe the actual behavior, so the summary should not promise lifecycle behavior the option does not implement.
 /// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition also matches a consumer that explicitly pins C# 7.3, so the package silently overrides that explicit choice despite the comment saying explicit choices are never overridden. That can change compilation semantics for the consumer's own source. Only supply latest when LangVersion is unset; an explicitly incompatible version should remain intact and fail with a clear compatibility diagnostic.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs:34

  • The PR description states that only FormatterUtilities.cs and Json.Deserializers.cs change on the shared server side, but this hunk rewrites the server transport framing, and the diff also changes IMessageFormatter, Json.cs, Json.TestNodeSerializer.cs, and a shared polyfill. Please update the description and server-side test summary so reviewers and release notes reflect the actual compatibility surface being changed.
 // The read side deliberately does NOT use a StreamReader. Content-Length is declared in UTF-8 *bytes*
// (see WriteRequestAsync), so the body must be consumed as bytes and decoded afterwards. A StreamReader
// hands out decoded characters, which for multi-byte UTF-8 content are fewer units than the declared
// length: the reader under-reads the frame, leaves its tail in the stream, and the framing permanently
// desynchronizes from the next frame onwards. Reading the headers through a StreamReader and the body
// from BaseStream would be worse still, because the reader's internal buffer would have already
// swallowed part of the body. Headers and body are therefore both read through this one byte-level
// buffer, so nothing can be buffered on the other side of the boundary.

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:355

  • The transform writes these generated files under obj but never records them in @(FileWrites), so MSBuild's Clean target does not know to remove them. Register the transformed outputs after the task, as other generated targets in this repository do (for example Microsoft.Testing.Platform.MSBuild.targets:56).
 <!-- Write the transformed copies to obj. -->
<_MtpClientTransformSource Files="@(_MtpClientTransformed)" />

The server-mode IMessageFormatter/MessageFormatter/Json.Deserialize<T>
overloads changed from ReadOnlyMemory<char> to ReadOnlyMemory<byte> (the
byte/char framing fix). Record that in net/InternalAPI.Unshipped.txt so
PublicApiAnalyzers stops reporting the removed char overloads (RS0017) and
the new byte overloads (RS0016): *REMOVED* the three char signatures that
net/InternalAPI.Shipped.txt still lists, and declare the three byte ones.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 4, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • ReadNumber does not fully mirror Jsonite as documented: Jsonite falls back to decimal for integral values outside ulong but within decimal (JsonReader.cs:519-523), while this fallback converts them to double and loses precision. Preserve that integer case before using GetDouble().
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • Appending CS0436 to the consumer project's global NoWarn suppresses every source-vs-imported-type conflict in adopter code, not only collisions from this package's polyfills. Scope the suppression to the transformed package source (for example, via a generated #pragma) or exclude only the colliding polyfills so unrelated conflicts remain visible.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs:36

  • This describes behavior the client does not implement: with the default false, discover/run return without sending exit, and callers/tests explicitly call ExitAsync. State that this value is only advertised during initialization and that request sequencing and shutdown remain the caller's responsibility.
 /// <summary>
/// Gets or sets a value indicating whether the client keeps the connection alive for multiple requests
/// (<c>capabilities.testing.isStateful</c> / <c>experimental_multiRequestSupport</c>). When
/// <see langword="false"/> the client performs a single discover or run and then exits. Defaults to
/// <see langword="false"/>.

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:26

  • This condition cannot distinguish the framework's 7.3 default from a consumer that explicitly pinned C# 7.3, so the package silently overrides an explicit project choice despite the comment and package documentation. Provide the conditional default from a packaged .props file ('$(LangVersion)' == '') so the consumer project can override it, and keep late composition logic in .targets.
 <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion>

src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs:263

  • Only send $/cancelRequest when cancellation actually wins the completion race. Currently, if the response completes and the token fires before the pending entry is removed, TrySetCanceled fails but a stale cancel notification is still sent for an already-completed request.
 pending.Completion.TrySetCanceled(cancellationToken);
// Best-effort notify the server to stop the in-flight work.
_ = SendCancelNotificationAsync(id);

Resolve the InternalAPI.Unshipped.txt conflict by keeping both sides: the
server-mode Deserialize byte-signature updates from this branch and the
AsyncConsumerDataProcessor constructor entry from main.
The FormatterUtilitiesTests and Json.TestNodeSerializer auto-merges reconcile
cleanly: main added tests that route through the private Deserialize<T>(string)
helper, which this branch changed to convert to UTF-8 bytes on NETCOREAPP.
Verified on the merged tree: full pack build green (0 warnings, 0 errors),
Microsoft.Testing.Platform.ServerClient.Source packs, and the ServerMode
FormatterUtilities tests pass 40/40 on net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 519bdc9e-ace7-46fa-aa7e-d07eeef955f6
CopilotAI review requested due to automatic review settings August 6, 2026 08:18

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated no new comments.

Suppressed comments (7)

src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs:326

  • This fallback does not actually match Jsonite for integral values beyond UInt64: Jsonite next returns decimal (JsonReader.cs:515-520), while this converts the token to double and loses precision. Preserve the remaining integer-token case as decimal before using the floating-point fallback.
 return element.GetDouble();

src/Platform/Microsoft.Testing.Platform.ServerClient/build/Microsoft.Testing.Platform.ServerClient.Source.targets:49

  • NoWarn is a project-wide compiler setting, so merely referencing this package suppresses every CS0436 in the adopter's own code and can hide unrelated source/import type conflicts. Scope the suppression to the generated package files instead—for example, prepend #pragma warning disable CS0436 in the source transform—and leave the consumer's global warning policy unchanged.
 <NoWarn>$(NoWarn);CS0436</NoWarn>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:328

  • This second generated project path has the same argument-splitting problem when the asset root contains spaces. Quote it before passing the command to dotnet build.
 $"build {testAsset.TargetAssetPath}/PackagedConsumer -c {Constants.BuildConfiguration}",

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:87

  • Globbing the entire repository polyfill set is not safe for a source-injected package. On modern .NET many of these files take their #else branch and emit assembly-level TypeForwardedTo attributes (for example IsExternalInit.cs:19 and RequiredMemberAttribute.cs:25), so they do not “compile to nothing” and instead add exported type forwarders to every adopter assembly. Down-level, only the OS and Range/Index files have EXCLUDE_* guards, so an adopter that already defines common source polyfills gets duplicate-type errors that NoWarn=CS0436 cannot suppress. Curate package-safe polyfills or add package-specific guards, and cover a consumer with existing source polyfills plus public-API analysis.
 <Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />

src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj:64

  • The package-specific text needs to lead the description, with $(CommonProductDescription) appended last. This is the repository's stated pack metadata convention (Directory.Build.targets:65-66) and is followed by peer platform packages such as Microsoft.Testing.Extensions.HtmlReport.csproj:11-13; hard-coding the shared sentence first also lets this package drift when the shared description changes.
 <PackageDescription>
<![CDATA[Microsoft Testing is a set of platform, framework and protocol intended to make it possible to run any test on any target or device.
This is a source-only package: it injects (as internal source) a client for the Microsoft Testing Platform (MTP) server-mode JSON-RPC protocol, sharing the exact protocol and serialization source the platform server compiles. It has no runtime dependency and is native-AOT friendly (Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET).]]>
</PackageDescription>

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageConsumerTests.cs:157

  • The generated asset path is not quoted, so this build command is split incorrectly whenever the repository or temporary asset root contains spaces. Quote the project path as the other acceptance-test build invocations do.
 $"build {testAsset.TargetAssetPath}/HostileConsumer -c {Constants.BuildConfiguration}",

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientPackagedConsumerRunTests.cs:314

  • This generated project path is unquoted, so the acceptance test cannot build from a checkout or asset directory containing spaces. Pass the path as one quoted command-line argument.

This issue also appears on line 328 of the same file.

 $"build {testAsset.TargetAssetPath}/DummyApp -c {Constants.BuildConfiguration}",

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
CopilotAI review requested due to automatic review settings August 6, 2026 16:22
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review August 6, 2026 16:24
CopilotAI reviewed Aug 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7194280a-9169-44e0-a35c-466c64385a12
@github-actions

Copy link
Copy Markdown
Contributor

Parallel-safety audit — PR #10085

Scope note: the workflow's pre-extracted file/line-range lists were unavailable in this run, so I pulled the PR diff directly via the GitHub API. Almost every changed test file in this PR is newly added, so the primary/pre-existing distinction mostly collapses: findings below are primary unless explicitly marked pre-existing/context.

Step 0 — Parallelization state per affected assembly

AssemblyOpt-in sourceEffective scopeWorkersAnalyzer coverage
Microsoft.Testing.Platform.ServerMode.Client.Sources.UnitTests (new, added by this PR)[assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in new Program.csMethodLevel0 (CPU count)Coverable once MSTEST0074‐0077 ship (plain attribute, compiler-visible) — not active today, only MSTEST0073 ships on main
Microsoft.Testing.Platform.UnitTests (existing, ServerMode/*Tests.cs modified)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in its own Program.csMethodLevel0Unchanged by this PR
MSTest.Acceptance.IntegrationTests (existing, new file added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR
Microsoft.Testing.Platform.Acceptance.IntegrationTests (existing, 2 new files added)Pre-existing [assembly: Parallelize(Scope = MethodLevel, Workers = 0)] in Program.csMethodLevel0Unchanged by this PR

No .runsettings/testconfig.json/MSBuild override was found for any of these assemblies, and this PR touches no Directory.Build.props/.targets. MethodLevel means both intra-class and cross-class conflicts would be live in every assembly this PR adds tests to — so the isolation quality of the new tests matters.

Findings

No Critical/High findings. The new tests follow strong isolation patterns throughout:

  • Ephemeral ports, not fixed ports (good pattern, not a finding). Both FakeMtpServer (unit tests) and TcpMessageHandlerTests.ConnectedHandlers (existing project, new helper) bind via new TcpListener(IPAddress.Loopback, 0). Port 0 is OS-assigned, so concurrent instances never collide — this correctly avoids what would otherwise be a category-B shared-fixed-resource hazard under MethodLevel.
  • Per-test fixture instantiation. Every method in MtpServerClientTests.cs (~30 methods) creates its own using FakeMtpServer server = new(); — no shared mutable fixture across methods, no [ResourceLock]/[DoNotParallelize] needed or missing.
  • Child-process environment, not process-global.MtpServerClientAcceptanceTests.CreateOptions() and MtpServerClientPackagedConsumerRunTests.CreateChildEnvironment() both build a Dictionary<string, string?> passed into a launched child process's environment (MtpServerClientOptions.EnvironmentVariables, or DotnetCli.RunAsync(..., environmentVariables: ...)). Neither calls Environment.SetEnvironmentVariable on the current test-host process, so this is not a category‐A finding — the current process's environment/CWD is never mutated.
  • Read-only shared static field — not a hazard.MtpServerClientSourcePackageTests has private static readonly SourcePackage Package = SourcePackage.Load(); shared across its test methods. SourcePackage.Load() only reads a .nupkg from artifacts/packages/<Configuration>/Shipping (via ZipFile.OpenRead) once, and every subsequent access is read-only (Package.AllEntries, Package.PackedCsByTfm, ...). No mutation, so no [DoNotParallelize] is needed for this class despite the repo convention about shared mutable generated assets — this asset is immutable after load.
  • Isolated NuGet restore per test.MtpServerClientPackagedConsumerRunTests/MtpServerClientSourcePackageConsumerTests use Path.Combine(testAsset.TargetAssetPath, ".nuget-packages") — a path unique to each test's own TestAsset (via AssetName/GenerateAssetAsync), not a shared fixed path across methods — so no category‐B collision.
  • Context/Info only: the new TestSetup.cs[AssemblyInitialize] calls SerializerUtilities.RegisterClientSerializers(), which mutates a shared static registration dictionary. This is assembly-fixture code, serialized once by MSTest's own semaphore before any worker runs — not a live race — and the production method itself uses double-checked locking (ClientSerializersLock + volatile flag), so it's also safe if ever invoked from elsewhere. No action needed.
  • Context/Info only:Environment.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", "1") in the new Program.cs executes as a top-level statement before the test host starts, mirroring every other MSTest-based unit-test Program.cs in this repo — one-time process bootstrap, not a per-test mutation, so not a live category-A race.

Category D (over-serialization)

No over-serialization concerns: no new [DoNotParallelize] was added on a method/class that didn't need it, and no unnecessarily broad [ResourceLock] was introduced. All Workers values found are either 0 (CPU count) or explicit positive counts pre-existing in ParallelExecutionTests.cs/ResourceLockExecutionTests.cs, none touched by this PR.

Bottom line

This PR introduces a new MethodLevel-parallel test assembly plus new tests in three existing MethodLevel-parallel assemblies. I found no process-global-state races, no shared-path collisions, and no [ResourceLock]/[DoNotParallelize] declaration mismatches — the new tests consistently isolate their shared resources (ephemeral ports, per-test fixtures, child-process env vars, immutable cached artifacts). No changes are recommended from a parallel-safety standpoint.

(Cross-ref: testability/smell/anti-pattern concerns, if any, are covered by the sibling detect-static-dependencies/test-smell-detection/test-anti-patterns analyses and are out of scope here.)

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 179.4 AIC · ⌖ 3.5 AIC · ⊞ 24.6K · [◷]( · )

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the package architecture, source transforms, compatibility matrix, concurrency, cancellation, and end-to-end behavior after the merge-readiness fixes. The remaining findings were addressed and the targeted unit, package-consumer, and cross-platform validation is green.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10085

GradeTestMutationNotesHow to improve
B (80–89)new MtpServerClientSourcePackageConsumerTests.
HostileConsumer_
CompilesAgainstPackedSource
N/ASingle ExitCode==0 assertion is appropriate for a compile oracle, but stderr diagnostics aren't asserted beyond the failure message.Also assert result.StandardError is empty/does not contain "error" to catch warnings-as-errors silently swallowed by a non-zero-but-untested path.
A (90–100)new MtpServerClientAcceptanceTests.
DiscoverAndRun_
ViaSourcePackageClient_
ReportsExpectedTestNode
N/ATwo independent client sessions (discover, then run) with precise ContainsSingle assertions and descriptive failure messages.
A (90–100)new MtpServerClientPackagedConsumerRunTests.
PackagedConsumer_
LaunchesRealServer_
DiscoversAndRunsExpectedNode
N/AEnd-to-end build + run gate asserts exit code and each discrete stdout marker (DISCOVERED/EXECUTED/OK), giving good failure isolation.

Summary: Three new acceptance tests were added covering the new Microsoft.Testing.Platform.ServerMode.Client.Sources package: an in-repo client acceptance test, a packaged-consumer end-to-end run test, and a hostile-consumer compile oracle. All three follow existing acceptance-test conventions (asset generation, Assert.AreEqual/Assert.Contains/Assert.ContainsSingle with descriptive messages, isolated NuGet caches to avoid stale-package false passes). No swallowed exceptions, no tautological assertions, and no reliability/isolation issues were found (each test uses its own generated asset directory). No inline suggestions were posted — the sole noted improvement is a minor enhancement rather than a defect.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 61.5 AIC · ⌖ 3.4 AIC · ⊞ 16.7K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 3a64386 into mainAug 7, 2026
42 of 43 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the nohwnd-mtp-client-source-package branch August 7, 2026 01:11
Jakub Jareš (nohwnd) added a commit to microsoft/vstest that referenced this pull request Aug 14, 2026
…ient.Sources (#16300)
* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source
testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.
Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.
The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.
Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.
* Commit the interim local MTP client feed so restore works everywhere
NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.
The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.
🤖
* Order remote NuGet feeds before the interim local feed in test asset restore
The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.
Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.
🤖
* Key MTP environment variable dictionary case-insensitively on Windows
Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.
🤖
* Consume official B-fixed MTP client source drop (testfx#10085)
Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).
Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.
MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.
🤖
* Align interim MTP client pin to the coordinator's canonical numberfix drop
Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.
Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).
🤖
* Add MTP converter/options unit tests and fix numeric and trait coercion
The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.
Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.
Three fixes fall out of writing them:
- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
bad line number into a plausible-looking wrong answer. Range-check instead so
the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
formatters box JSON scalars differently, so a numeric or boolean trait was
silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
which seven other vstest call sites already use and which also traces the
override.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Fix MTP client shutdown and fail loudly on a missing node uid
Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:
- Exit was awaited on the run's own cancellation token. Cancelling or aborting
a run is exactly when that token is already cancelled, so ExitAsync threw
immediately and the graceful shutdown handshake was skipped in the one case
it matters most.
- The await was unbounded, so a test application that never acknowledges exit
would hang discovery or execution indefinitely. The notification it replaced
could not block at all.
Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.
The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.
Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.
Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Add non-ASCII MTP acceptance coverage for UTF-8 frame length
MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.
vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.
Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.
These fail until the fix lands upstream in testfx.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Narrow the non-ASCII MTP test name to the BMP and fix the collector count
Running the acceptance test revealed two things worth recording.
First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.
Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.
Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.
MtpUnderVstestTests: 16/16 on both console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the MTP client drop with the Content-Length framing fix
Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.
That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.
Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.
Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Repin the interim MTP client to the uniquely-named utf8fix1 drop
Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.
Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.
The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.
Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Address expert review feedback on the MTP hardening
Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.
Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.
Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.
Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.
Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.
Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.
Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the latest MTP client drop from testfx#10297
Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.
Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
still good on both formatter paths.
The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
* Consume the published MTP client package; drop the interim local feed
testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:
- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
which only existed to make a local-folder source restore alongside the
remote https feeds. With no local folder it reverts to the simple base.
The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.
The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.
🤖
* Enable the MTP testhost in the non-ASCII acceptance test
RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.
🤖
* Reject fractional MTP line numbers
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Reject selected MTP nodes without UIDs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nohwnd@Evangelink@azat-msft