Skip to content

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

Draft
nohwnd wants to merge 18 commits into
mainfrom
nohwnd-mtp-client-source-package
Draft

Add a source-only MTP server-mode client package#10085
nohwnd wants to merge 18 commits into
mainfrom
nohwnd-mtp-client-source-package

Conversation

@nohwnd

@nohwnd 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 every consumer that drives an MTP test app writes its own client. There are already three: the minimal Jsonite-based one in vstest, and a richer StreamJsonRpc-based one that lives (duplicated) in VSUnitTesting and in the C# Dev Kit (vs-green). This adds one canonical client, owned here in testfx next to the protocol it talks to, and ships it as a source-only package so the three consumers can drop their own copies.

What's here (the testfx leg)

  • A new project, src/Platform/Microsoft.Testing.Platform.ServerClient, that links the server's own protocol and serialization source (RpcMessages, JsonRpcMethods, SerializerUtilities, the Jsonite parser, and the .NET-only System.Text.Json engine) and adds the client-only transport and API on top (IMtpServerClient, MtpServerClient, the JSON-RPC connection, and the process launcher). Because it reuses the server's encoder, wire compatibility holds by construction.
  • Serialization stays dependency-free and native-AOT friendly: Jsonite on .NET Framework / netstandard2.0, in-box System.Text.Json on .NET. No StreamJsonRpc.
  • It packs as a source-only package, Microsoft.Testing.Platform.ServerClient.Source: the linked and client source ships as contentFiles/cs/<tfm>/** with BuildAction=Compile, no DLL and no runtime dependency, compiled into each consumer as internal types. The pack target projects the final compiled 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).

Two shared server files change: FormatterUtilities.cs and Json.Deserializers.cs (an object[] deserializer plus a notification-params raw-dict fallback the net8 STJ path needed). Both are behavior-preserving for the server; server serialization tests stay green (56/56).

Tests

  • Unit tests stand up an in-memory paired-Stream fake server and exercise initialize / discover / run / run-with-filter, notifications, cancellation, malformed frames, and disconnect on both formatter paths. Green on net8 (STJ) 21/21 and net462 (Jsonite) 21/21.
  • An acceptance test drives a real generated MTP app end to end (launch → initialize → discover → run → exit) and asserts the exact discovered / passed node, for net462, net8.0, and net10.0 child assets. The net462 child (Jsonite server) against the net8 STJ client is the real cross-formatter wire proof.
  • A contract test 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. This is the anti-drift guard: nobody can add a source file without packing it, or ship a file the project does not compile.

Scope

This is the testfx leg only. Adopting the package in vstest, VSUnitTesting, and vs-green (deleting their bespoke clients and re-homing their glue on the package API) are separate follow-up PRs in those repos. Kept as a draft while those are lined up and the package version and changelog are finalized.

🤖

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.

🤖
Copilot AI review requested due to automatic review settings July 20, 2026 13:07

Copilot AI 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
File Description
TestFx.slnx Registers the new projects.
test/UnitTests/.../TestSetup.cs Registers client serializers for tests.
test/UnitTests/.../Program.cs Configures the test executable.
test/UnitTests/.../MtpServerClientTests.cs Tests client protocol behavior.
test/UnitTests/.../Microsoft.Testing.Platform.ServerClient.UnitTests.csproj Configures multi-TFM unit tests.
test/UnitTests/.../FakeMtpServer.cs Implements the loopback fake server.
test/UnitTests/.../BannedSymbols.txt Enforces MSTest assertions.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs Exercises real MTP applications.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj References the client project.
test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MtpServerClientSourcePackageTests.cs Validates package contents.
src/Platform/Microsoft.Testing.Platform/.../Json.Deserializers.cs Adds generic arrays and notification parameters.
src/Platform/Microsoft.Testing.Platform/.../FormatterUtilities.cs Selects Jsonite outside .NETCoreApp.
src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs Supplies minimal resource strings.
src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Documents package usage.
src/Platform/Microsoft.Testing.Platform.ServerClient/Microsoft.Testing.Platform.ServerClient.csproj Defines linked sources and source-only packing.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/SerializerUtilities.ClientSerializers.cs Adds client serialization directions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs Launches and manages MTP processes.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientOptions.cs Defines client configuration.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClientExceptions.cs Defines client exceptions.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerClient.cs Implements the high-level client.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpJsonRpcConnection.cs Implements JSON-RPC correlation and dispatch.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs Defines the client API and models.
src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpClientLogger.cs Defines client diagnostics abstractions.

Comment thread TestFx.slnx
Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
nohwnd added 2 commits July 20, 2026 15:20
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.

🤖
Copilot AI review requested due to automatic review settings July 20, 2026 13:25

Copilot AI 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" />

Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs Outdated
Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs Outdated
@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.
Copilot AI review requested due to automatic review settings July 20, 2026 14:32

Copilot AI 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.

🤖
Copilot AI review requested due to automatic review settings July 20, 2026 14:46

Copilot AI 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.

compiles from ..\Microsoft.Testing.Platform\, guaranteeing wire compatibility by construction
(single source of truth). On top of that it adds the client-only transport + API.

PACKAGING: this ships as a source-only NuGet package (Microsoft.Testing.Platform.ServerClient.Source).

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.

Could we name this Microsoft.Testing.Platform.ServerMode.Client.Sources and align the project/assembly name as well? ServerClient is ambiguous, while the namespace and documented capability already call this the server-mode client. I would not use the shorter Microsoft.Testing.Platform.Client.Sources yet: with multiple MTP protocols, that name implies a protocol-agnostic client and would make a future second client awkward. ServerMode identifies the supported protocol without unnecessarily making JSON-RPC or TCP part of the package contract. The plural .Sources also follows the more common Microsoft source-package convention.


_connection.NotificationReceived += OnNotificationReceived;
_connection.ServerRequestHandler = OnServerRequestAsync;
_connection.Start();

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.

Starting the read loop in the constructor creates a subscription gap: Launch returns only after this line, so callers cannot attach TestNodesUpdated, LogReceived, TelemetryReceived, or AttachmentsReceived before messages may arrive. Request-driven test updates are normally safe because callers subscribe before calling discover/run, but unsolicited startup log or telemetry notifications can be silently lost. Could construction and startup be separated (or notifications buffered until initialization/subscription) so consumers can wire handlers before reading begins?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct observation, and it is scoped to unsolicited notifications. The constructor wires the client's own handlers before _connection.Start(), so the client never misses a connection notification — the window is only between the client raising its public event and the consumer subscribing to it.

For the request-driven paths (discover / run and their node-update / log / attachment notifications) that is safe: the consumer subscribes to the events, then issues the request that causes the server to emit them, so they cannot arrive before the subscription. This is the ordering guarantee documented on IMtpServerClient.

The only genuinely racy case is a notification the server emits unsolicited right after connect, before any request (startup telemetry, for example). MTP does not stream that ahead of initialize/discover/run today, so it is not a v1 blocker for the core scope. Deferring _connection.Start() out of the constructor into an explicit start step the consumer calls after wiring events is a reasonable follow-up for the opt-in telemetry/debugger scenarios; leaving this open as a design decision.

🤖

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.

🤖
Copilot AI review requested due to automatic review settings July 21, 2026 08:55

Copilot AI 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.

🤖
Copilot AI review requested due to automatic review settings July 21, 2026 09:10
Copilot AI review requested due to automatic review settings July 21, 2026 11:04
nohwnd added a commit to nohwnd/vstest that referenced this pull request Jul 21, 2026
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.

🤖

Copilot AI 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 39 out of 39 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (18)

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

  • The new generic number decoding assumes every JSON number is an Int32. Normal test-node updates serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170), so a fractional duration makes GetInt32() throw; the notification is then converted to invalid params and silently ignored by the client. Preserve integer/long/floating-point values in both generic dictionary and array decoders, and cover a fractional-duration update.
                    case JsonValueKind.Number:
                        items.Add(element.GetInt32());

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

  • This idempotence check is not thread-safe. Two concurrent Launch calls can enter registration together, and one sets the flag before the dictionaries are populated; the other can return and create a formatter that snapshots a partial serializer set. Protect the entire registration with synchronization and publish the registered state only after all entries are installed.
        if (s_clientSerializersRegistered)
        {
            return;
        }

        s_clientSerializersRegistered = true;

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

  • These consumer requirements contradict the package being produced: the project and contract test explicitly ship the polyfills (except Index/Range), and the down-level OperatingSystem polyfill uses C# 14 extension blocks, while the compile oracle sets LangVersion=preview. Update the README so adopters do not select C# 9 or add duplicate polyfills based on incorrect guidance.
- 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
  avoid duplicate-type collisions with the polyfills consumers already have.

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

  • This new C# file is UTF-8 without a BOM, but .editorconfig:65-68 requires utf-8-bom for every *.cs file. Resave it with the required BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/Program.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:65-68 requires utf-8-bom for every *.cs file. Resave it with the required BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/MtpServerClientTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:65-68 requires utf-8-bom for every *.cs file. Resave it with the required BOM.
    test/UnitTests/Microsoft.Testing.Platform.ServerClient.UnitTests/FakeMtpServer.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:65-68 requires utf-8-bom for every *.cs file. Resave it with the required BOM.
    test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MtpServerClientAcceptanceTests.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required BOM.
    src/Platform/Microsoft.Testing.Platform.ServerClient/PlatformResourcesShim.cs:1
  • This new C# file is UTF-8 without a BOM, but .editorconfig:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required 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:65-68 requires utf-8-bom for every *.cs file. Resave it with the required BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/Client/MtpServerProcess.cs Outdated
nohwnd added 2 commits July 21, 2026 13:23
…matter guard

The source package must not force the platform to carry a renamed Jsonite namespace.
Revert the source rename back to origin/main and instead rewrite the vendored top-level
`namespace Jsonite` (plus `using Jsonite;` and qualified `Jsonite.`) to
Microsoft.Testing.Platform.ServerMode.JsonRpc.Json.Jsonite inside the pack-time transform.
The platform keeps top-level `namespace Jsonite`, so the rename lives only in the packed
copies and never fights main. The "Jsonite" wire Id and JsoniteProperties are left untouched.

FormatterUtilities selected the formatter with `#if NETSTANDARD2_0`, which mis-selects the
System.Text.Json branch (ReadOnlyMemory<char>, Json.Json) when the file is shipped as source
and compiled on net462, where NETSTANDARD2_0 is not defined. Generalize it to `#if !NETCOREAPP`
to match the IMessageFormatter interface guard, which already uses `#if NETCOREAPP`. Behavior
is identical for the platform, which only ever compiles this file as netstandard2.0 or .NET.

Add a contract test asserting the packed Jsonite namespace is package-qualified, not top-level.

🤖
Copilot AI review requested due to automatic review settings July 21, 2026 11:45

Copilot AI 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 28 out of 28 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (2)

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

  • The new generic number decoder accepts only Int32. MTP serializes time.duration-ms as TimeSpan.TotalMilliseconds (a double in Json.TestNodeSerializer.cs:170), and notification objects recurse through the same generic dictionary/array decoding. A normal fractional duration therefore makes GetInt32() throw and fails the client's entire read loop. Decode JSON numbers as int, long, or floating-point as appropriate in both generic-number branches.
                    case JsonValueKind.Number:
                        items.Add(element.GetInt32());

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

  • Registration is not thread-safe. The flag is set before the ordinary dictionaries are fully populated, so two concurrent Launch calls can let one thread return here and construct a formatter while the other is still mutating/enumerating those dictionaries. This can produce missing serializers or an InvalidOperationException; protect the complete registration block with one-time synchronized initialization.
        if (s_clientSerializersRegistered)
        {
            return;
        }

<Compile Include="$(MTPDir)ServerMode\JsonRpc\MessageFormatException.cs" Link="Linked\ServerMode\JsonRpc\MessageFormatException.cs" />
<!-- Transport: the LSP-style framing engine, reused verbatim by the client. -->
<Compile Include="$(MTPDir)ServerMode\JsonRpc\IMessageHandler.cs" Link="Linked\ServerMode\JsonRpc\IMessageHandler.cs" />
<Compile Include="$(MTPDir)ServerMode\JsonRpc\TcpMessageHandler.cs" Link="Linked\ServerMode\JsonRpc\TcpMessageHandler.cs" />

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is a real latent bug. The read path rents a char buffer sized by the Content-Length value and reads that many characters, but Content-Length is the UTF-8 byte count (the write path encodes it from Encoding.UTF8.GetByteCount). For any message containing multi-byte UTF-8 the two disagree, so ReadBlockAsync over-reads into the next frame and the framing desyncs.

It has not surfaced because the request traffic that flows through here in practice is ASCII (method names, uids, filter expressions), but non-ASCII test names or error text in server-to-client messages can hit it.

TcpMessageHandler is a shared platform file the live server also uses, and the fix is a behavioral change to that transport — read commandSize bytes and UTF-8-decode them (symmetric with the write path), plus a non-ASCII round-trip test. That belongs in a dedicated platform PR rather than this source-package PR, so I am leaving this thread open to track it.

🤖

Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/PACKAGE.md Outdated
NuGet runs the TargetsForTfmSpecificContentInPackage pass once per TFM and
dispatches those passes in parallel. The transform output is TFM-independent
(the same bytes for every TFM), so both passes computed the same destination
paths under obj\...\mtpclientsrc\ and raced to write them. On the parallel
Windows CI builds that surfaced as MSB4018 "_MtpClientTransformSource failed:
IOException: the process cannot access the file because it is being used by
another process" (Windows Debug + Release). Linux/macOS build only the net8.0
leg, so a single content pass never collided there.

Fix: nest the transform output under the current TFM (mtpclientsrc\<tfm>\) so
the parallel passes write to distinct directories. The dir is computed in a
target-local PropertyGroup so $(TargetFramework) is the per-TFM value at
execution time. The packaged contentFiles/cs/<tfm>/ layout is unchanged; the
hostile-consumer oracle and the anti-drift contract test stay green.

🤖
Copilot AI review requested due to automatic review settings July 21, 2026 12:20

Copilot AI 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 28 out of 28 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (23)

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

  • This registration is not thread-safe. Two concurrent Launch calls can both enter this method; worse, one sets the flag before populating the dictionaries, allowing the other call to create a formatter from a partially registered serializer set. Guard registration with a lock (or Lazy/one-time initialization) and publish the registered state only after all dictionary updates complete.
        if (s_clientSerializersRegistered)
        {
            return;
        }

        s_clientSerializersRegistered = true;

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

  • Generic protocol numbers are decoded with GetInt32, but test-node notifications serialize TimingProperty.GlobalTiming.Duration.TotalMilliseconds as a double (Json.TestNodeSerializer.cs:168-170). A normal fractional duration therefore throws while decoding a node update and terminates the client read loop. Decode arbitrary JSON numbers (for example int/long/double as appropriate) in both this array decoder and the nested dictionary decoder.
                    case JsonValueKind.Number:
                        items.Add(element.GetInt32());

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

  • Linking TcpMessageHandler makes its framing bug part of the new client: Content-Length is emitted as a UTF-8 byte count, but ReadAsync passes that value to StreamReader.ReadBlockAsync as a character count. Jsonite leaves ordinary non-ASCII characters unescaped, so a Unicode test name/log causes the client to wait for extra characters or consume the next frame. The framing reader needs to read exactly the declared byte count and then UTF-8 decode it; add a Unicode round-trip test on the Jsonite leg.
    <Compile Include="$(MTPDir)ServerMode\JsonRpc\TcpMessageHandler.cs" Link="Linked\ServerMode\JsonRpc\TcpMessageHandler.cs" />

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

  • If a consumer-provided logger throws here, FailAllPending is never reached, leaving every outstanding request hung. The same pattern occurs in the notification/request error paths, and DelegateMtpClientLogger permits arbitrary throwing delegates. Make diagnostic logging best-effort (via a non-throwing helper) and fail pending requests before attempting to log.
        catch (Exception ex)
        {
            _logger.Log(MtpClientLogLevel.Error, $"MTP client read loop failed: {ex}");
            FailAllPending(new MtpServerClientException("The MTP client read loop failed.", ex));

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

  • These consumer requirements contradict the package being produced. The project explicitly packs the polyfills (and contract tests require them), while OperatingSystem.cs uses the C# extension-block syntax, so C# 9 is insufficient; the compile oracle masks this by setting LangVersion=preview. Either lower the shipped syntax/remove the polyfills to honor this contract, or document the actual language and polyfill behavior.
- 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
  avoid duplicate-type collisions with the polyfills consumers already have.

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

  • This package target appends CS0436 to the consuming project's global NoWarn, suppressing unrelated source/import type conflicts in all consumer code. Scope the suppression to the injected files (for example, add a #pragma warning disable CS0436 in the pack-time preamble) rather than changing the consumer's diagnostics globally.
    <NoWarn>$(NoWarn);CS0436</NoWarn>

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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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

  • This new C# file is UTF-8 without a BOM, but .editorconfig:66-67 requires charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with 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 charset = utf-8-bom for every *.cs file. Re-save it as UTF-8 with BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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

  • Relative source paths are combined with a working directory derived from that same path. For example, bin/Debug/app.dll produces cwd bin/Debug and the fallback argument bin/Debug/app.dll, so the child resolves bin/Debug/bin/Debug/app.dll (the apphost path has the same problem). Normalize source to an absolute path before calling BuildLaunch.
        LaunchCommand launch = BuildLaunch(source, port);

The net8 System.Text.Json path decoded every JSON number in an untyped
object slot (property-bag values and generic array elements) with
GetInt32(). Real MTP notifications carry non-Int32 numbers - durations as
doubles, timestamps and counts as longs - so GetInt32() threw
FormatException and faulted the whole client read loop. The net462 Jsonite
path decodes numbers generically and was unaffected, which is why only the
net8 axis failed.

Decode generically to match Jsonite: int, then long, then ulong, else
double. This is a pure superset - values that used to decode as int still
do; only the ones that used to throw now widen. Added two regression tests
that deserialize a dictionary and an array carrying an int, a long and a
double, covering both fixed call sites.
Copilot AI review requested due to automatic review settings July 21, 2026 14:14

Copilot AI 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 29 out of 29 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (21)

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

  • This registration is not safe when two clients are launched concurrently. The first caller sets the flag before populating the shared Dictionary instances, so a second caller can return and create a formatter while the serializer set is only partially registered; concurrent dictionary mutation/enumeration can also fail. Synchronize the entire registration operation and publish the completed state only after all entries are installed.
        if (s_clientSerializersRegistered)
        {
            return;
        }

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

  • A non-null value returned by ServerRequestHandler cannot generally be sent. The shared ResponseMessage serializer calls SerializerUtilities.SerializeObject(result), but primitives and arbitrary dictionaries are not registered serializers, so returning (for example) true or a raw result bag throws and this catch only logs it, leaving the server waiting forever. Either constrain the callback to a supported result type or add raw JSON-result serialization and cover a non-null response.
            await WriteMessageAsync(new ResponseMessage(request.Id, result), cancellationToken).ConfigureAwait(false);

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

  • This suppresses every CS0436 warning in the consuming project, including unrelated type conflicts in the consumer's own code. The package already transforms each injected source file, so scope the suppression to those generated files (for example with a generated-source pragma) or avoid injecting the conflicting polyfill rather than changing the consumer-wide NoWarn.
    <NoWarn>$(NoWarn);CS0436</NoWarn>

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

  • The documented C# 9 minimum is too low for the shipped source. Client files use C# 12 primary constructors and collection expressions, and the down-level OperatingSystem polyfill uses a C# 14 extension block; the compile-oracle test hides this by setting LangVersion=preview. Document the actual compiler requirements so consumers do not get syntax errors.
- C# language version 9 or later.

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

  • This says the package does not ship polyfills, but the project includes src/Polyfills/**/*.cs in the package and the package contract tests require Polyfills/Ensure.cs and Polyfills/OperatingSystem.cs. Since this README is packed into the NuGet, consumers receive incorrect conflict/opt-out guidance.
- 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
  avoid duplicate-type collisions with the polyfills consumers already have.

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

  • This new C# file is saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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

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

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

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

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

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

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

  • This new C# file is saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please 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 saved without a UTF-8 BOM, while .editorconfig:66-67 requires charset = utf-8-bom for all C# files. Please resave it with a BOM.
// Copyright (c) Microsoft Corporation. All rights reserved.

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

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

nohwnd and others added 3 commits July 21, 2026 19:27
…n faults

Route every transport and lifetime log site through a SafeLog helper so a consumer logger that throws can no longer fail a request, skip process teardown, or fault the read loop. Make the JSON-RPC frame body write atomic under cancellation so a cancel mid-body cannot corrupt Content-Length framing. Race the loopback accept against child-process exit so a server that dies before dialing back no longer hangs. Absolutize the app path before the working-directory change, debug-log the swallowed SocketExceptions on stop and dispose, lock the client-serializer registration and publish the ready flag last, and pass Dictionary params straight through the serializer. Also documents the ctor serializer-registration precondition and the stateful/advertised-only option.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…atform solution filter

PACKAGE.md now states the packed source needs C# 12 (collection expressions), not C# 9, and drops the stale 'add your own polyfills' guidance since the package ships them. Adds the ServerClient project and its unit-test project to Microsoft.Testing.Platform.slnf so the filtered view builds them.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds wire-level unit tests for run and run-with-filter argument serialization and the Dictionary passthrough, a FakeMtpServer dispose fix plus request-capture and server-request helpers, and a hostile-consumer oracle assertion pass. Registers the full MTP provider set in the unit-test entry point so the CI-appended report and dump options are accepted, and switches the contract test to Assert.HasCount to satisfy the analyzer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 21, 2026 17:28

Copilot AI 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 30 out of 30 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

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

  • This does not fully mirror Jsonite as documented. Jsonite falls back to decimal for an integer outside ulong (for example 18446744073709551616), while this falls through to double, changing the boxed type and losing integer precision. Try parsing the raw integer as decimal before using GetDouble(); use NumberStyles.Integer so fractional/exponent values still follow Jsonite's double path.
        return element.GetDouble();

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

  • This package-wide target suppresses CS0436 for the consumer's entire compilation, not only for the injected polyfill files. Referencing this package can therefore hide unrelated source-vs-assembly type conflicts in the consumer's own code. Keep the suppression local to the generated package sources (for example via a pragma in the pack-time preamble), rather than appending CS0436 to the consuming project's global NoWarn.
    <NoWarn>$(NoWarn);CS0436</NoWarn>

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

  • The linked framing handler writes Content-Length as a UTF-8 byte count, but ReadAsync passes that value to StreamReader.ReadBlockAsync as a character count. Any server notification containing non-ASCII text (for example a Unicode test display name or error) therefore waits for extra characters or consumes bytes from the next frame, hanging/desynchronizing this new client. The transport must read exactly the advertised number of bytes and decode that byte buffer as UTF-8; the current ASCII-only tests do not exercise this.
    <Compile Include="$(MTPDir)ServerMode\JsonRpc\TcpMessageHandler.cs" Link="Linked\ServerMode\JsonRpc\TcpMessageHandler.cs" />

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

  • The source package is intended to compile directly in net462, but ProcessStartInfo.Environment is not part of the .NET Framework 4.6.2 reference surface (only EnvironmentVariables is). The Windows net462 consumer leg therefore fails to compile this injected file. Use the cross-framework property and handle null as removal.
            startInfo.Environment[variable.Key] = variable.Value;

Comment thread src/Platform/Microsoft.Testing.Platform.ServerClient/Client/IMtpServerClient.cs Outdated
The opt-in ServerRequestHandler returned Task<object?>, but the client's response serializer is registered only for the concrete Dictionary<string, object?> the formatter writes. A handler that returned any other type (a DTO, or even a non-Dictionary IDictionary such as SortedDictionary) threw on serialize, and the connection only logs that failure, so the server waited forever for a response that never came.

Constrain the handler result to IDictionary<string, object?>? as a compile-time guard against arbitrary types, and normalize any non-concrete IDictionary to Dictionary<string, object?> at runtime before writing so the connection always answers. All types stay internal, so there is no public API change. Adds a SortedDictionary round-trip regression test on both formatter axes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 06:41

Copilot AI 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 30 out of 30 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

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

  • This still does not match Jsonite for integral values above ulong.MaxValue. Jsonite next tries decimal (Jsonite/JsonReader.cs:519-523), while this falls through to double, changing the boxed type and losing integer precision (for example 18446744073709551616). Preserve a decimal fallback for integer-form tokens before using double for fractions/exponents, and cover that boundary in JsonTests.
        if (element.TryGetUInt64(out ulong ulongValue))
        {
            return ulongValue;
        }

        return element.GetDouble();

Comment on lines +134 to +138
<ItemGroup Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) != '.NETCoreApp'">
<Compile Remove="$(MTPDir)ServerMode\JsonRpc\Json\Json.cs" />
<Compile Remove="$(MTPDir)ServerMode\JsonRpc\Json\Json.Deserializers.cs" />
<Compile Remove="$(MTPDir)ServerMode\JsonRpc\Json\Json.Serializers.cs" />
<Compile Remove="$(MTPDir)ServerMode\JsonRpc\Json\Json.TestNodeSerializer.cs" />
// serialize and the server would wait forever for a response that never comes.
if (result is null or Dictionary<string, object?>)
{
return result;

@Evangelink Evangelink left a comment

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.

Review — source-only MTP server-mode client package

I ran this through several passes (expert MTP reviewer + a bug-focused diff reviewer) and then validated every finding by hand against ba4463f.

Overall the engineering quality here is high and I want to say that up front: the unit tests are genuinely behavioural rather than tautological (a real paired-stream fake server covering malformed frames, disconnect, cancellation and server-initiated requests on both formatter paths), the packed-vs-compiled contract test is a genuinely good idea, and most of the description's claims hold up.

Claims I verified as true, so they don't need re-checking:

  • FormatterUtilities.cs #if NETSTANDARD2_0#if !NETCOREAPP is behaviour-identical for the platform build (SupportedNetFrameworks is all .NETCoreApp, plus netstandard2.0, so the two conditions are equivalent there).
  • TcpMessageHandler's range-operator → Substring change is semantically identical, and the System.Range/ValueTuple rationale is correct.
  • The OperatingSystem.cs #if NETFRAMEWORK branch is unreachable from the platform's own TFMs.
  • ReadNumber is a strict superset: every value that used to decode as int still does; only ones that used to throw FormatException now widen. It's also genuinely needed — SerializerUtilities.TestNodeSerializers.cs:218 emits time.duration-ms as a double.
  • No init accessors, no new public types, no TODOs, BOMs present, solution files updated in all three places, BannedSymbols.txt correctly bans N:AwesomeAssertions.
  • PendingRequest.Completion correctly uses TaskCreationOptions.RunContinuationsAsynchronously, so TrySetResult can't inline a caller's continuation onto the read loop.

The findings below are what survived. The two on the .targets are the ones I'd resolve before this goes out, because they're about the packaging contract rather than the code — and the packaging contract is the entire point of shipping it this way.

constant in your project rather than relying on this suppression, e.g.:
<DefineConstants>$(DefineConstants);EXCLUDE_OS_POLYFILL;EXCLUDE_RANGE_INDEX_POLYFILL</DefineConstants>
-->
<NoWarn>$(NoWarn);CS0436</NoWarn>

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.

Blocking — the .targets never sets LangVersion, so a stock consumer can't compile the injected source.

The SDK defaults LangVersion to C# 7.3 for net462 and netstandard2.0. The injected source is authored under the repo's LangVersion=preview and uses file-scoped namespaces (C#10), records (C#9), is not null (C#9), collection expressions and primary constructors (C#12), and nullable annotations (C#8).

Worse, the injected src/Polyfills/OperatingSystem.cs uses a C# 14 extension block on exactly the down-level branch:

#if !NETCOREAPP && !EXCLUDE_OS_POLYFILL
...
    extension(OperatingSystem)   // C# 14

So vstest adding <PackageReference Include="Microsoft.Testing.Platform.ServerClient.Source" /> to a net462 project that doesn't override LangVersion gets dozens of CS8370-class errors with nothing pointing at the package. And because it's extension(...), no LangVersion value at all makes it compile on a .NET 9 SDK — the package silently requires .NET 10.

The compile oracle papers over this. MtpServerClientSourcePackageConsumerTests.cs:43-45 is deliberately hostile on ImplicitUsings, Nullable and TreatWarningsAsErrors, but then hands the package the one property the package should be supplying itself:

<!-- The injected source uses modern C# (records, file-scoped namespaces). The down-level TFMs
     would otherwise default to an old LangVersion; a real adopter (vstest) already builds latest. -->
<LangVersion>preview</LangVersion>

Suggested: set <LangVersion Condition="'$(LangVersion)' == '' or '$(LangVersion)' == '7.3'">latest</LangVersion> in the .targets, and drop the C# 14 dependency from the shipped polyfill (OperatingSystem.cs is only needed for TcpMessageHandler.cs:215's IsBrowser() — an ordinary internal static class with plain static methods puts the floor back at C# 10/12). Then remove LangVersion from the oracle, which is the whole point of the oracle.

On net462 / netstandard2.0 the package injects down-level polyfills of compiler/BCL attributes
(nullable annotations in System.Diagnostics.CodeAnalysis, Microsoft.CodeAnalysis.EmbeddedAttribute,
etc.). A consumer that already provides the same types (for example through a referenced assembly)
will get the benign CS0436 "the type in source conflicts with the imported type; using the one in

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.

Major — NoWarn=CS0436 is justified for polyfill marker attributes, but it also silences shadowing of the platform's public message types.

The comment reasons entirely about "marker attributes … behaviorally identical", which is fair for those. But the package also injects Microsoft.Testing.Platform\Messages\*.cs (csproj:153) — Microsoft.Testing.Platform.Extensions.Messages.TestNode, TestNodeUpdateMessage, PropertyBag, the whole property model — in their original namespaces. TestNode is public class TestNode in the shipping platform assembly.

Every named adopter (vstest, VSUnitTesting, C# Dev Kit) also references Microsoft.Testing.Platform. In those assemblies a source-declared internal TestNode now shadows the referenced public one, source wins, and the only compiler signal is suppressed.

This PR demonstrates the collision is real — MSTest.Acceptance.IntegrationTests.csproj needed:

<ProjectReference Include="...Microsoft.Testing.Platform.ServerClient.csproj" Aliases="serverclient" />

with a comment saying it exists so the internal TestNode/TestNodeUpdateMessage "never clash". extern alias only works for assembly references. A source-only consumer has no equivalent escape hatch, so the workaround the repo itself needed is structurally unavailable to the package's target audience. And HostileConsumer doesn't reference Microsoft.Testing.Platform, so the oracle never exercises the configuration all three adopters are actually in.

The failure isn't at adoption time — it's later: the first time the platform adds a member to public TestNode and vstest sets it, the code binds to the stale internal copy and misbehaves at the boundary, with CS0436 suppressed.

Suggested: apply the namespace rewrite you already do for Jsonite (csproj:305-307) to the injected platform types too (Microsoft.Testing.Platform.Microsoft.Testing.Platform.ServerClient.Internal.). That removes the collision class entirely and lets NoWarn=CS0436 shrink to a per-file #pragma on the polyfills. Failing that, at minimum add a HostileConsumer variant that also references Microsoft.Testing.Platform, so the oracle covers the real adopter shape.

// System.Range.GetOffsetAndLength, which pulls in System.Range/System.ValueTuple —
// neither is in-framework on net462, where this file is also compiled as source into
// the dependency-free MTP client package. Substring is behavior-identical.
_ = int.TryParse(line.Substring(ContentLengthHeaderName.Length).Trim(), out contentSize);

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.

Major — Content-Length is a UTF-8 byte count, but the body is read as that many chars. The Jsonite writer this PR ships makes that reachable.

Write side (line 166-171 / 187-188):

int byteCount = Encoding.UTF8.GetByteCount(messageStr);
await _writer.WriteLineAsync($"Content-Length: {byteCount}")

Read side (line 70-84):

char[] commandCharsBuffer = ArrayPool<char>.Shared.Rent(commandSize);
Memory<char> memoryBuffer = new(commandCharsBuffer, 0, commandSize);
await _reader.ReadBlockAsync(memoryBuffer, cancellationToken)

This is latent today only because the .NET path serializes with Utf8JsonWriter, whose default encoder escapes all non-ASCII to \uXXXX — so bytes == chars by accident.

The Jsonite writer this PR ships to net462 / netstandard consumers does not. Json/Jsonite/JsonWriter.cs:296 writes any non-surrogate char ≥ ' ' verbatim:

else
{
    writer.Write(c);
}

So a net462 consumer (the stated vstest target) calling RunTestsWithFilterAsync("/*/*/*/Tésts/*") or RunTestsAsync(["Ns.Класс.Test"]) emits a frame whose Content-Length exceeds its char count. The reader then consumes N extra chars past the end of the frame, eating the start of the next frame's headers, and the connection is permanently desynced.

I realise the framing code is pre-existing — but this PR's premise is "the wire is byte-for-byte identical to the server's expectations", and the net462-child acceptance test is explicitly billed as the cross-formatter proof. That premise doesn't hold for non-ASCII test names, which are not exotic.

Correct fix is to read Content-Length bytes off BaseStream (looping until filled) and decode as UTF-8. Narrower workaround: make the Jsonite writer escape non-ASCII to match Utf8JsonWriter.

Separately, contentSize is never validated: int.TryParse leaves it 0 on garbage (infinite loop risk), a negative value reaches ArrayPool.Rent and throws, and a huge value is an unbounded allocation off untrusted input. Worth a range check while you're here.

// Keep its params as a raw property bag so the client can decode them itself,
// instead of dropping them. For the server this only affects unknown methods,
// which it ignores anyway.
_ => value.ValueKind == JsonValueKind.Object

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.

Major — this fallback makes the IDictionary binder reachable for arbitrary methods, and that binder throws ArgumentException on duplicate JSON keys — which is outside the catch filter.

The IDictionary<string, object?> deserializer above builds its bag with items.Add(kvp.Name, ...) (lines 29-47). JsonDocument.EnumerateObject() yields duplicate property names verbatim, and Dictionary<K,V>.Add throws System.ArgumentException on a duplicate key.

The catch filter immediately below is:

catch (Exception ex) when (ex is MessageFormatException or InvalidOperationException or JsonException)

ArgumentException isn't in it. Before this change, _ => null meant an unknown method's params were never dict-bound; now any method the server doesn't strongly-type routes untrusted params through that binder.

So {"jsonrpc":"2.0","id":7,"method":"testing/somethingNew","params":{"a":1,"a":2}} produces an ArgumentException that propagates out of Json.Deserialize<RpcMessage> → out of TcpMessageHandler.ReadAsync (whose filter only handles SocketException/IOException with ConnectionReset) → into ServerTestHost.MessageLoop, whose only catch is OperationCanceledException. The message loop faults and the server-mode session dies, where previously the unknown method was simply ignored.

One-character fix in both this binder and the new object[] one: items[kvp.Name] = ... (last-wins) instead of .Add. Belt and braces, add ArgumentException to the catch filter.

Minor related note: this widens a server file to serve the client, when SerializerUtilities.ClientSerializers.cs:122-156 already establishes the client-only-override pattern. Overriding the RpcMessage deserializer client-side would keep the server's decode surface untouched — worth considering given how load-bearing that loop is.

try
{
await WriteMessageAsync(new RequestMessage(id, method, @params), cancellationToken).ConfigureAwait(false);
return await pending.Completion.Task.ConfigureAwait(false);

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.

Major — a request issued after the read loop has already exited hangs forever.

FailAllPending is only reachable from inside ReadLoopAsync (118/133) and from Dispose. There's no terminal/closed state, so once the read loop has returned, nothing will ever complete a newly-registered PendingRequest.

Concrete: a stateful (MultiRequestSupport) client. RunTestsAsync completes; the test host then exits or crashes; the read loop sees message is null, calls FailAllPending over an empty map, and returns. The caller then calls DiscoverTestsAsync. On TCP the first write after the peer's FIN lands in the send buffer and succeeds — the RST only surfaces on a later write — so WriteMessageAsync doesn't throw. Then await pending.Completion.Task never completes.

There's no backstop: MtpServerClientOptions has no per-request timeout, and InitializeAsync/DiscoverTestsAsync/RunTestsAsync all default to CancellationToken.None. The caller hangs until something else disposes the client.

There's a narrower version of the same race even while the loop is alive: FailAllPending can run between _pendingRequests[id] = pending and the await, and the new entry is missed.

Suggested: latch Exception? _closedReason when the read loop exits or Dispose runs; have SendRequestAsync fail fast if it's already set, and re-check it immediately after inserting into _pendingRequests, failing the pending request if it closed in between. A default per-request timeout on MtpServerClientOptions would be a good second layer.

IMessageFormatter formatter = FormatterUtilities.CreateFormatter();

var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();

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.

Minor — the listener is started outside the try, so it leaks on several throw paths; and the accepted socket can leak on the timeout race.

Everything from listener.Start() here down to the try at line 144 is unprotected. BuildLaunch (109) can throw on a malformed path; the EnvironmentVariables copy (125-128) can throw on a null key. Either leaves a bound, listening loopback socket alive for the life of the process — and in a long-lived IDE host that accumulates.

Second, narrower leak at 160-175: if the child dials back in the window between acceptTask.Wait(...) returning false and the throw, acceptTask completes with a live TcpClient that is never assigned to acceptedClient and so never disposed (the catch sees null). The socket survives to finalization and the child stays connected to a dead listener.

Suggested: move listener.Start() inside the try, and in the catch attach acceptTask.ContinueWith(t => t.Result.Dispose(), TaskContinuationOptions.OnlyOnRanToCompletion) — which also observes the fault on the disposed-listener path.

if (!process.HasExited)
{
#if NETCOREAPP
process.Kill(entireProcessTree: true);

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.

Minor — on .NET Framework this kills only the child, and neither branch waits for exit.

Kill(entireProcessTree: true) doesn't exist on net462, so the #else kills just the direct child. An MTP app launched with test host controllers (--hangdump, --crashdump, --retry) spawns its own test host child — those become orphans on exactly the TFM this package exists to support.

Also, Dispose returns immediately after SafeKill; nothing waits for the process to actually go away, so a caller that disposes and then deletes the test app's output directory can hit a file lock.

Suggested: on NETFRAMEWORK, enumerate and kill children via Win32_Process/ParentProcessId (or at least document the limitation), and add a bounded WaitForExit after SafeKill.

Two small related things in this file: EnableRaisingEvents = true is set at line 130 but Exited is never subscribed; and stdout is drained (BeginOutputReadLine with no handler — which does correctly drain the pipe) but discarded, so an app that reports its startup failure on stdout gives the caller nothing, while GetStandardError only surfaces stderr.

new ClientCapabilities(_options.DebuggerProvider, _options.IsStateful));

ResponseMessage response = await _connection.SendRequestAsync(JsonRpcMethods.Initialize, args, cancellationToken).ConfigureAwait(false);
MtpServerCapabilities capabilities = DecodeCapabilities(response.Result as IDictionary<string, object?>);

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.

Minor — a protocol-shaped failure decodes as "no capabilities" instead of an error.

MtpServerCapabilities capabilities = DecodeCapabilities(response.Result as IDictionary<string, object?>);

as + DecodeCapabilities's result ??= new Dictionary<string, object?>() means a result that isn't a dictionary silently yields supportsDiscovery: false, multiRequestSupport: false, …. The caller can't distinguish "server genuinely has no capabilities" from "we failed to decode the handshake".

RunCoreAsync at 239 does the same thing (?? new Dictionary<string, object?>() before Deserialize<RunResponseArgs>).

Since this client is explicitly the wire-compatibility contract between testfx and three separate consumers, a serialization regression showing up as silently-degraded capabilities is the worst failure mode available — the adopter sees features quietly turn off, not a stack trace. Suggest throwing MtpServerClientException with the actual response.Result type when the cast fails.

=> value switch
{
int i => i,
long l => (int)l,

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.

Minor — AsInt truncates unchecked and silently drops two of the four types ReadNumber can now produce.

long l => (int)l,

An unchecked narrowing cast that returns a wrong value rather than failing. And now that ReadNumber (this PR's Json.Deserializers.cs change) can hand back ulong or double, both fall through to _ => null — so a processId that arrives as a double (entirely possible from a JS-based client-side producer, or from Jsonite's own numeric widening) decodes as "no process id".

Suggested: add ulong and double cases with range checks, and use checked (or long l when l is >= int.MinValue and <= int.MaxValue) rather than a silent truncation.

(ambient refs such as System.ValueTuple) is validated later during packaging / vstest adoption.
-->
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

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.

Nit — AllowUnsafeBlocks=true doesn't appear to be needed.

There's no unsafe or stackalloc anywhere in the linked source set, the client sources, or the shipped polyfills. For a package whose whole pitch is "dependency-free source you compile into your own assembly", a compiler relaxation like this is exactly the line a security reviewer at an adopting team will stop on. Worth removing unless something specific needs it.

Sign up for free to 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.

3 participants