Skip to content

[fix] Fix LoggerRunSettings verbosity being silently overridden when using --settings - #16044

Open
Jakub Jareš (nohwnd) wants to merge 165 commits into
mainfrom
fix/issue-10369-8a2bbfcf1b265926
Open

[fix] Fix LoggerRunSettings verbosity being silently overridden when using --settings#16044
Jakub Jareš (nohwnd) wants to merge 165 commits into
mainfrom
fix/issue-10369-8a2bbfcf1b265926

Conversation

@nohwnd

Copy link
Copy Markdown
Member

Summary

🤖 This is an automated fix generated by the Issue Triage agent.

Fixes #10369

Root Cause

When running dotnet test --settings my.runsettings with <Verbosity>normal</Verbosity> inside <LoggerRunSettings>, the verbosity is silently overridden and always set to minimal.

Two cooperating issues:

1. TestTaskUtils.CreateCommandLineArguments always injects verbosity

Even when a settings file is provided, the MSBuild VSTest task auto-injects --logger:Console;Verbosity=minimal (or whatever MSBuild-derived verbosity applies). This happens regardless of whether the user configured the console logger in their settings file.

2. LoggerUtilities.AddLoggerToRunSettings discards existing Configuration

When processing the injected --logger:Console;Verbosity=minimal, AddLoggerToRunSettings finds the existing console logger in the LoggerRunSettings (from the .runsettings file), removes it, and replaces it with the newly-constructed one — losing the user's <Verbosity>normal</Verbosity> configuration.

Fix

Part 1 — TestTaskUtils.cs

When isRunSettingsEnabled = true (a settings file is in use), omit Verbosity=X from the auto-injected logger argument. The settings file is the authoritative source for the logger configuration.

Part 2 — LoggerUtilities.cs

In AddLoggerToRunSettings: when the incoming logger has no Configuration (i.e. no CLI parameters were supplied for it) but the existing logger in runsettings does have a Configuration, preserve the existing Configuration rather than discarding it.

Tests

  • TestTaskUtils unit tests: CreateArgumentShouldNotInjectVerbosityWhenSettingsFileIsProvided and CreateArgumentShouldInjectVerbosityWhenNoSettingsFileIsProvided
  • EnableLoggersArgumentProcessor unit tests: ExecutorInitializeShouldPreserveExistingConfigurationWhenNoNewParametersAreProvided and ExecutorInitializeShouldOverrideExistingConfigurationWhenNewParametersAreProvided

Behavior Change

  • Before: dotnet test --settings my.runsettings with <Verbosity>normal</Verbosity> would always show minimal test output regardless of the settings file.
  • After: The verbosity from the settings file is respected.

🔍 Triaged by Issue Repro Triage & Auto-Fix 🔍

Copilot AI lite review requested due to automatic review settings May 19, 2026 01:48

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

This PR fixes an interaction between the MSBuild VSTest task and LoggerRunSettings where dotnet test --settings ... could silently override a console logger’s configured verbosity (e.g., forcing minimal even when the runsettings specified normal). It does so by (1) stopping MSBuild from injecting Verbosity=... when a settings file is used, and (2) preserving existing logger <Configuration> when a logger is re-added without new parameters.

Changes:

  • Update MSBuild task argument construction to omit Verbosity=... on the auto-injected --logger: when --settings is provided.
  • Update LoggerUtilities.AddLoggerToRunSettings to preserve an existing logger’s <Configuration> when the incoming logger has no configuration.
  • Add unit tests covering both the MSBuild argument behavior and the runsettings merge behavior.

Reviewed changes

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

File Description
src/Microsoft.TestPlatform.Build/Tasks/TestTaskUtils.cs Avoid injecting console logger verbosity when a runsettings file is in use.
src/vstest.console/Processors/Utilities/LoggerUtilities.cs Preserve existing logger configuration when re-adding a logger without CLI parameters.
test/Microsoft.TestPlatform.Build.UnitTests/TestTaskUtilsTests.cs Tests for verbosity injection behavior with/without --settings.
test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs Tests ensuring configuration is preserved/overridden appropriately when enabling loggers.

@nohwnd Jakub Jareš (nohwnd) left a comment

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.

Review: RunSettings Logger Verbosity Fix

The two-part fix is logically sound and well-targeted at the reported bug. Tests cover the primary scenarios clearly.

One finding worth discussing (see inline):

The isRunSettingsEnabled guard in TestTaskUtils.cs applies to both VSTestTask (Console logger) and VSTestTask2 (MSBuildLogger). For the Console logger this is the intended fix; for MSBuildLogger it silently drops the MSBuild-derived verbosity when a settings file is in use and the file doesn't configure that logger — which is the common case. This may be acceptable by design, but it's worth a conscious decision and an explicit test.

No issues found in:

  • LoggerUtilities.cs — the Configuration preservation is correct. The check logger.Configuration is null && existingLogger.Configuration is not null is precisely scoped. It also applies to explicit --logger:console CLI invocations (not just the MSBuild task path), which is consistent and desirable.
  • Null safety, public API surface, IPC, or cross-TFM concerns — none present.

🧠 Reviewed by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

vsTestVerbosity = "normal";
}
else if (quietTestLogging.Contains(taskVsTestVerbosity))
builder.AppendSwitchUnquotedIfNotNull("--logger:", loggerToUse);

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.

[Backward Compatibility — VSTestTask2]

The isRunSettingsEnabled branch applies to both VSTestTask (Console logger) and VSTestTask2 (MSBuildLogger). For VSTestTask2 + settings file, the MSBuild-derived verbosity is now silently dropped:

  • Before: --logger:Microsoft.TestPlatform.MSBuildLogger;Verbosity=minimal (or the MSBuild-mapped value)
  • After: --logger:Microsoft.TestPlatform.MSBuildLogger (no verbosity — falls back to the logger's internal default)

If the settings file doesn't contain a <Logger> entry for Microsoft.TestPlatform.MSBuildLogger (the common case), the MSBuildLogger will ignore the MSBuild-derived verbosity entirely. This is a silent behavior change for anyone using VSTestTask2 (i.e., dotnet test via the MSBuild tooling task) with a .runsettings file.

The fix for the Console logger (the bug's subject) is correct; consider whether VSTestTask2 should be treated the same way or whether it needs to retain the MSBuild verbosity pass-through when no settings-file configuration is present for it. A test covering VSTestTask2 + settings file would make the intent explicit.

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.

Good catch. The fix now scopes the verbosity-suppression to task is VSTestTask only. VSTestTask2 (MSBuildLogger) always injects the MSBuild-derived verbosity regardless of whether a settings file is present, since MSBuildLogger verbosity is driven by MSBuild rather than user settings. A test covering VSTestTask2 + settings file is also added to make the intent explicit.

🔧 Iterated by PR Iteration Agent 🔧

@nohwnd

Copy link
Copy Markdown
Member Author

Commit pushed: 7930a74

🔧 Iterated by PR Iteration Agent 🔧

@nohwnd Jakub Jareš (nohwnd) left a comment

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.

Review: LoggerRunSettings Verbosity Fix — Updated

The previous finding (VSTestTask2 verbosity silently dropped when a settings file is present) has been addressed in the follow-up commit. The task is VSTestTask type guard correctly scopes the "no verbosity injection" path to the Console logger only — VSTestTask2 (MSBuildLogger) continues to inject the MSBuild-derived verbosity regardless of whether a settings file is present, which is the intended semantics.

Full analysis of the two-part fix:

Part 1 — TestTaskUtils.cs: The branching logic is correct. When isRunSettingsEnabled && task is VSTestTask, only --logger:Console is injected (no verbosity), deferring to the .runsettings configuration. The else path (VSTestTask2 or no settings file) preserves the original verbosity-injection behavior. The three new tests cover all cases: VSTestTask with settings, VSTestTask without settings, VSTestTask2 with settings.

Part 2 — LoggerUtilities.cs: The logger.Configuration is null && existingLogger.Configuration is not null guard is precisely scoped. It correctly preserves .runsettings Configuration when no new parameters are supplied (whether from the MSBuild task path or from a plain --logger:console CLI invocation), and lets explicit CLI parameters like --logger:console;verbosity=quiet override as before. Null safety is sound — the existingLoggerIndex >= 0 guard makes the array access safe.

Description alignment: Accurate. Both root causes and both fix components are described correctly. The test names in the description match the actual test names.

No issues found.


🧠 Reviewed by Expert Code Reviewer 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

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

Comment on lines +553 to +556

var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance);
executor.Initialize("console"); // no verbosity param — simulates "--logger:Console" from MSBuild

Comment on lines +560 to +564
<LoggerRunSettings>
<Loggers>
<Logger friendlyName=""console"" enabled=""True"">
<Configuration>
<Verbosity>normal</Verbosity>
@nohwnd

This comment has been minimized.

@nohwnd

Copy link
Copy Markdown
Member Author

Commit pushed: 59a900e

🔧 Iterated by PR Iteration Agent 🔧

@nohwnd

This comment has been minimized.

@nohwnd

This comment has been minimized.

@nohwnd

This comment has been minimized.

@nohwnd

This comment has been minimized.

@nohwnd

This comment has been minimized.

@nohwnd

Copy link
Copy Markdown
Member Author

Commit pushed: 9283b20

🔧 Iterated by PR Iteration Agent 🔧

Copilot AI review requested due to automatic review settings June 11, 2026 20:58
@nohwnd

This comment has been minimized.

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 124 out of 125 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs: Language not supported

Comment on lines +135 to +142
case string s: writer.WriteStringValue(s); break;
case int i: writer.WriteNumberValue(i); break;
case long l: writer.WriteNumberValue(l); break;
case double d: writer.WriteNumberValue(d); break;
case float f: writer.WriteNumberValue(f); break;
case bool b: writer.WriteBooleanValue(b); break;
case short s: writer.WriteNumberValue(s); break;
case ushort us: writer.WriteNumberValue(us); break;
Comment on lines 806 to +808
<trans-unit id="MalformedRunSettingsKey">
<source>One or more runsettings provided contain invalid token</source>
<target state="translated">提供的一或多個 runsettings 包含無效的語彙基元</target>
<target state="translated">已提供的一或多個 runsettings 中含有無效的 Token</target>
Comment thread eng/Versions.props
Comment on lines 16 to 18
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<VersionPrefix>18.8.0</VersionPrefix>
<VersionPrefix>18.9.0</VersionPrefix>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
Comment on lines +70 to +82
// NativeAOT publish can take several minutes.
var exited = process.WaitForExit(TimeSpan.FromMinutes(10));
Assert.IsTrue(exited, "dotnet publish timed out after 10 minutes.");

// Ensure all async output has been drained before reading the buffer.
process.WaitForExit();

var output = outputBuilder.ToString();

// Publish must succeed.
Assert.AreEqual(0, process.ExitCode,
$"dotnet publish failed with exit code {process.ExitCode}.\n\nOutput:\n{output}");

@nohwnd

Copy link
Copy Markdown
Member Author

Commit pushed: c529206

🔧 Iterated by PR Iteration Agent 🔧

@nohwnd

This comment has been minimized.

@nohwnd

This comment has been minimized.

… build to fix SymbolCheck (main) (#16323)

* Bump Microsoft.VisualStudio.Diagnostics.Utilities to 18.3.11611.365

The pin at 18.3.11401.5 is not from a VS release branch, so its DLLs have
no symbols on the symbol server and SymbolCheck fails on every insertion.

This property drives three packages - Diagnostics.Utilities,
Enterprise.AspNetHelper and ArchitectureTools.PEReader - whose DLLs are
bundled into the CLI vsix.

18.3.11611.365 is already used for MicrosoftInternalTestPlatformExtensions
in this same file, so it is a known-good release branch build.

Forward-port of #16322 (rel/18.10) so 18.11 does not regress.

* Correct version to 18.3.11527.243 (verified to have symbols)

18.3.11611.365 was chosen because a sibling property already used it, but symchk against msdl shows it has no symbols published - same failure as the original pin.

18.3.11527.243 is the newest version where all three packages pass symchk.
* Gate MTP under vstest behind an opt-in flag

🤖

* Use the conventional MTP testhost feature flag

🤖

* Default the MTP testhost to disabled

🤖
* Update dependencies from https://github.com/dotnet/arcade build 20260724.3
On relative base path root
Microsoft.DotNet.Arcade.Sdk From Version 11.0.0-beta.26369.1 -> To Version 11.0.0-beta.26374.3

* Update dependencies from https://github.com/dotnet/arcade build 20260731.1
On relative base path root
Microsoft.DotNet.Arcade.Sdk From Version 11.0.0-beta.26369.1 -> To Version 11.0.0-beta.26381.1

* Update dependencies from https://github.com/dotnet/arcade build 20260807.8
On relative base path root
Microsoft.DotNet.Arcade.Sdk From Version 11.0.0-beta.26369.1 -> To Version 11.0.0-beta.26407.8

---------

Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com>
* Fix logger concurrency correctness and TRX write reliability

Both TrxLogger and HtmlLogger register event handlers that are invoked
concurrently during parallel test execution, but several pieces of their
mutable state were plain non-thread-safe collections.

TrxLogger:
- _runLevelErrorsAndWarnings is now a ConcurrentQueue<RunInfo>.
- _runLevelStdOut is now a ConcurrentQueue<string> instead of a StringBuilder;
  the run level informational message is materialized on read with the same
  AppendLine-per-message formatting as before.
- CreateTestRun had a check-then-act race that could create more than one
  TestRun. It is replaced by GetOrCreateTestRun, which uses double checked
  locking, and LoggerTestRun is published with Volatile so that a reader that
  sees a non-null reference also sees a fully initialized TestRun.
- TestElementAggregation._testLinks was a plain Dictionary mutated through a
  non-atomic ContainsKey + Add. Mutation and enumeration are now guarded by a
  lock. A lock is used rather than ConcurrentDictionary so that the insertion
  order of test links, which is meaningful for ordered tests, is preserved.
- PopulateTrxFile only caught UnauthorizedAccessException, so a disk full or
  any other IO failure silently dropped the TRX file with an unhandled
  exception. It now reports IOException, UnauthorizedAccessException,
  XmlException, NotSupportedException and SecurityException using the new
  localized TrxLoggerWriteFailed resource.

HtmlLogger:
- TestResultHandler used TryGetValue + TryAdd + ResultCollectionList.Add, which
  could publish duplicate collections for the same source. It now uses the
  atomic GetOrAdd and only the winning thread publishes the collection.
- ResultList, FailedResultList, InnerTestResults and the run level message
  lists are appended through lock protected helpers, so no result or message
  can be lost. The lists are still created lazily, so the null versus empty
  semantics observed by the serialized XML are unchanged.

Fixes #16320

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32aa1e90-526a-4b49-97cc-1297638d7520

* Correct two inaccurate code comments from review feedback

- TestRunDetails._syncLock no longer describes ResultCollectionList as
  lazily created, since that field is initialized inline. Only the run
  level message lists are created on first use.
- GetOrCreateTestRun no longer says Started is set to DateTime.Now in
  Initialize; TestRunStartTime is captured with DateTime.UtcNow.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32aa1e90-526a-4b49-97cc-1297638d7520

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32aa1e90-526a-4b49-97cc-1297638d7520
* Restore custom data collector platform tests

Use the existing out-of-process collector test asset through the supported discovery path, and complete its secondary event channel before ending each run.

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

🤖

* Copy collector from resolved project output

Use the ProjectReference target path instead of assuming its configuration matches the solution configuration.

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

🤖

* Always close collector event client

Reject invalid event ports and stop the secondary communication client even when setup or validation fails.

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

Copilot-Session: 74aae65f-0435-4727-b13a-ff1e80980b3f

🤖
* Revive socket client and server tests

Stop socket listeners deterministically and serialize socket server startup, accept, and shutdown so port 0 listeners cannot escape Stop.

Replace unbounded waits and stale post-close assertions, and re-enable the two stable socket test classes.

🤖

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

* Fix SocketServer accept lock scope

🤖

* Harden SocketServer shutdown regression

🤖

* Scope SocketServer shutdown handling

🤖

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ient.Sources (#16300)

* Retarget the MTP client onto Microsoft.Testing.Platform.ServerClient.Source

testfx now ships vstest's MTP server-mode JSON-RPC client as a source-only
package built from the MTP server's own protocol and serialization source, so
the wire format cannot drift from the server.

Delete vstest's transport core (MtpServerConnection, MtpJson, MtpConstants,
MtpClientHelpers) and retarget the glue onto the package's IMtpServerClient:
launch via MtpServerClient.Launch, drive Initialize/Discover/Run/Exit, read
node updates from the TestNodesUpdated event with typed MtpTestNodeUpdate
accessors, and bridge EqtTrace through DelegateMtpClientLogger.
MtpClientOptionsFactory centralizes option construction and log-level mapping.

The package is a compile-time source dependency (PrivateAssets=all), so no
runtime dependency and no public API are added.

Blocked on testfx publishing the package (microsoft/testfx#10085); references
an interim local feed, so CI cannot restore it yet.

* Commit the interim local MTP client feed so restore works everywhere

NuGet.config pointed local-mtp at the absolute path Q:\q\local-mtp-feed, which
is machine-local and does not exist in CI, so restore failed with an incorrect
path. Move the feed under the repo at eng/local-mtp-feed, point NuGet.config at
that repo-relative path, and commit the package into the feed. .gitignore keeps
ignoring *.nupkg but adds a negation for eng/local-mtp-feed/*.nupkg so the feed
package is tracked.

The package is the fresh Design-A drop of
Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev, which builds
CrossPlatEngine clean on net462, netstandard2.0, and net8.0 (0 errors, 0
warnings) with the retargeted glue. Interim only; remove the feed once
microsoft/testfx#10085 ships the package to a public feed.

🤖

* Order remote NuGet feeds before the interim local feed in test asset restore

The acceptance tests restore the TestAssets solution, which transitively
restores product projects like CrossPlatEngine that now reference the interim
local-mtp feed. Passing that local-folder feed to dotnet restore alongside the
remote https feeds triggered two NuGet quirks, both surfacing as NU1301: a
relative --source path is rooted at each restored project's directory, and a
local-folder source placed before the remote sources mis-normalizes the https
URLs into per-project relative paths.

Resolve relative local-folder sources to absolute paths and emit the remote
sources first so all local-folder sources come last; remote feeds keep their
configured order. Only needed while the MTP client package lives on the interim
local feed, and harmless once testfx#10085 ships it to a public feed.

🤖

* Key MTP environment variable dictionary case-insensitively on Windows

Both places that collect environment variables for the MTP application
launch now share one comparer: case-insensitive on Windows, case-sensitive
elsewhere. Before, the runsettings path used that comparer but the
data-collector-only path used a plain ordinal dictionary, so a run with no
runsettings variables but with data-collector variables lost the
case-folding the classic testhost path applied on Windows. The package
options dictionary is ordinal, so deduping here preserves the classic
Windows semantics before the values reach it.

🤖

* Consume official B-fixed MTP client source drop (testfx#10085)

Replaces the interim 2.4.0-dev pack with the official drop that fixes the
STJ number-decode bug: untyped JSON numbers were hard-cast to Int32, so node
bags carrying doubles (durations) or longs (timestamps) threw FormatException
and faulted the MTP read loop on the net8 client. The fix decodes numbers
generically (ReadNumber: TryGetInt32 -> TryGetInt64 -> TryGetUInt64 -> double).

Pinned to the unique version 2.4.0-dev.20260721161520 to avoid NuGet
same-version cache collisions while the package is served from the committed
local feed.

MtpUnderVstestTests: net11.0 (STJ) axis now 7/7 (was 0/7); net481 (Jsonite)
axis 5/7. The 2 remaining failures are a pre-existing net462 TRX-logger load
issue that also breaks classic non-MTP trx tests, unrelated to this retarget.

🤖

* Align interim MTP client pin to the coordinator's canonical numberfix drop

Swaps the interim feed pack and pin from the timestamped unique
2.4.0-dev.20260721161520 to the coordinator's canonical uniquely-named drop
2.4.0-dev.numberfix (MD5 FC7F7A9F68EF482718B61DC9DA5F38B4). Byte-equivalent
fixed content -- the packed net8 Json.Deserializers.cs decodes untyped JSON
numbers via ReadNumber at both sinks (L55/L97, helper L344), same as the prior
drop -- this only adopts the stable canonical interim identity the package
owner is standardizing on across consumers.

Validation unchanged: MtpUnderVstestTests net11.0 (STJ) axis 7/7, full suite
12/14 (the 2 remaining failures are the pre-existing net462 TRX-logger load
issue, unrelated to this retarget).

🤖

* Add MTP converter/options unit tests and fix numeric and trait coercion

The retarget onto Microsoft.Testing.Platform.ServerClient.Source left the MTP
glue with no unit coverage at all - the only tests were the end-to-end
MtpUnderVstestTests. The conversion code is now pure and dependency-free, so
cover it directly.

Add MtpTestNodeConverterTests and MtpClientOptionsFactoryTests (55 tests)
covering the normalized-Node contract, per-formatter number boxing, outcome
mapping, the action-node filter, vstest bridge properties, standard
output/error, traits, duration and log-level mapping.

Three fixes fall out of writing them:

- TryGetRawInt wrapped out-of-range values with unchecked((int)l), turning a
  bad line number into a plausible-looking wrong answer. Range-check instead so
  the property stays at its visibly-unset default.
- AddTraits collapsed every non-string trait value to an empty string. The two
  formatters box JSON scalars differently, so a numeric or boolean trait was
  silently dropped on one formatter and kept on the other. Format invariantly.
- MtpClientOptionsFactory re-read VSTEST_CONNECTION_TIMEOUT and hardcoded the
  90-second default instead of calling EnvironmentHelper.GetConnectionTimeout,
  which seven other vstest call sites already use and which also traces the
  override.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Fix MTP client shutdown and fail loudly on a missing node uid

Retargeting onto the source package changed exit from a fire-and-forget
notification into an awaited request/response call, which introduced two
regressions:

- Exit was awaited on the run's own cancellation token. Cancelling or aborting
  a run is exactly when that token is already cancelled, so ExitAsync threw
  immediately and the graceful shutdown handshake was skipped in the one case
  it matters most.
- The await was unbounded, so a test application that never acknowledges exit
  would hang discovery or execution indefinitely. The notification it replaced
  could not block at all.

Route both proxy managers through MtpServerClientFactory: TryExit runs on its
own bounded token, swallows failures (the caller disposes the client next,
which tears the process down regardless), and is called from a finally block so
a failed or cancelled run still shuts the application down.

The factory also exposes a replaceable Launch delegate so the managers can be
driven against a fake server in unit tests; production always uses
MtpServerClient.Launch.

Separately, BuildUids substituted FullyQualifiedName when a TestCase carried no
MTP.TestNode.Uid. The server projects node.Uid alone when building a run filter
and never reads any other field, so that substitution produced a filter
matching nothing: the run reported success having executed zero of the tests
the user selected, with no error anywhere. Throw instead, with a comment
explaining why no fallback is correct.

Adds 15 tests covering the shutdown paths, the uid filter, and both manager
flows against a fake MTP server.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Add non-ASCII MTP acceptance coverage for UTF-8 frame length

MTP frames declare Content-Length in UTF-8 bytes, but the transport shipped by
Microsoft.Testing.Platform.ServerClient.Source reads that number of characters:
it rents a char buffer of Content-Length and calls StreamReader.ReadBlockAsync.
For any frame carrying multi-byte UTF-8 the two disagree, so the reader
under-reads and leaves the body's tail to be parsed as the next frame's headers
- the connection desynchronizes from the following message onward.

vstest's deleted MtpServerConnection was byte-correct here (it read Content-Length
bytes into a byte[] and then UTF-8-decoded), so the retarget is a regression, not
an inherited defect. Client-to-server traffic is ASCII in practice, which is why
it has not surfaced; node updates flow the other way and carry user-authored test
names.

Give MtpMSTestProject a test whose display name mixes German umlauts (2 bytes
each), Japanese (3 bytes each) and an emoji (4 bytes, 2 chars), and mirror it in
MtpPureProject. Because the corruption lands on the message *after* the offending
one, its mere presence makes the whole run fail rather than just that test, so
every existing MTP scenario now exercises the transport with multi-byte content.
Adds a dedicated test asserting the name survives into the TRX.

These fail until the fix lands upstream in testfx.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Narrow the non-ASCII MTP test name to the BMP and fix the collector count

Running the acceptance test revealed two things worth recording.

First, an end-to-end MTP run cannot reproduce the Content-Length byte-vs-char
framing bug: the .NET MTP server serializes with System.Text.Json, whose default
encoder escapes every non-ASCII character to \\uXXXX, so the bytes on the wire
are ASCII and the byte count coincidentally equals the character count. The
framing bug is real but has to be proved at the unit level against the transport
directly, which is what the companion testfx change does. This test is therefore
a name-integrity guard, and its comments now say so rather than overclaiming.

Second, the emoji originally in the name exposed a separate defect: astral-plane
characters are escaped by System.Text.Json as a surrogate pair and arrive in the
TRX as the literal text \\ud83c\\udf89 instead of the character. BMP characters
decode correctly. That is its own bug, tracked separately, so the name is
narrowed to BMP multi-byte characters (umlauts 2 bytes, Japanese 3 bytes) which
still exercise the byte-denominated length without tripping over it.

Also updates the out-of-proc data collector's expected per-test-case attachment
count, which follows the test count.

MtpUnderVstestTests: 16/16 on both console axes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Consume the MTP client drop with the Content-Length framing fix

Replaces the interim local-feed pack with a build of microsoft/testfx#10297,
which stacks the Content-Length byte/char fix onto #10085. The transport now
reads exactly Content-Length bytes and UTF-8-decodes them, symmetric with the
write path, and reads the headers through the same byte-level buffer so no
StreamReader can buffer part of the body across the boundary.

That drop also carries #10085's ServerRequestHandler signature change (the
result is now constrained to a serializable dictionary), so FakeMtpServerClient
is updated to match.

Verification on this drop:
- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 140/140 (70 per axis, net11.0 and net481).
- MtpUnderVstestTests 16/16 on both console axes.

Note the 16/16: the two /logger:trx failures reported against the earlier drop
do not reproduce here, so they look like a local deployment issue rather than
anything in the retarget.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Repin the interim MTP client to the uniquely-named utf8fix1 drop

Swaps the interim feed pack from the plain 2.4.0-dev build output to the
coordinator's canonical 2.4.0-dev.utf8fix1 drop of microsoft/testfx#10297.

Byte-equivalent content: all 184 contentFiles are identical between the two
packs, including TcpMessageHandler.cs with both ReadExactlyAsync and the
TrimPreamble BOM tolerance. Only the version metadata differs.

The rename is the point. While the package is served from a committed local
folder, NuGet caches by version, so a plain 2.4.0-dev risks silently resolving a
stale cache entry from an earlier drop of the same name. The unique suffix makes
that impossible, matching the convention the branch already used for
2.4.0-dev.numberfix.

Re-verified from a cleared package cache: CrossPlatEngine clean on all three
TFMs, MTP unit tests 140/140, MtpUnderVstestTests 16/16.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Address expert review feedback on the MTP hardening

Localize the missing-uid error. The message reaches the user verbatim -
StartTestRun funnels ex.Message into HandleLogMessage(Error) - and every other
user-facing TestPlatformException in this assembly is resourced, so a hardcoded
English string formatted with CurrentCulture was self-contradictory. Adds
MtpTestCaseMissingNodeUid to Resources.resx, the generated designer property,
and a trans-unit to all 13 xlf files. The text now also states the remedy
(re-run discovery, or run without a selection) rather than only naming the
failure, and the comment records that aborting the whole source is deliberate:
silently running the addressable subset would recreate the same class of bug in
a smaller form.

Mark the three new test classes DoNotParallelize. MSTest parallelizes across
classes at MethodLevel by default here, and these classes mutate process-global
state - the MtpServerClientFactory.Launch seam and VSTEST_CONNECTION_TIMEOUT -
so a save/restore in TestInitialize/TestCleanup could restore one class's value
while another class's test was still relying on its own. That would have flaked
in CI looking like a product bug.

Close a hole in the float range guard. (float)int.MaxValue rounds *up* to
2147483648f, so comparing a float directly against int.MaxValue let that value
through and the cast then saturated - precisely the plausible-looking wrong
answer the guard exists to reject. Widen to double before comparing, and extend
the regression test to cover it.

Capture ProcessId before the exit handshake instead of reading it afterwards,
when the process may already be gone.

Test fixes: TryExitDoesNotUseAnAlreadyCancelledRunToken was vacuous (it built a
cancelled token it never passed anywhere) and LaunchDefaultsToTheRealClientLauncher
asserted only non-null, which any delegate satisfies. Both now assert something
that fails if the behaviour regresses. Adds the missing mixed-selection case,
where only some tests carry a uid.

Also fixes a stale test-count comment and softens an overclaim in
MtpPureProject, which no test currently references.

Unit tests 142/142 across net11.0 and net481; MtpUnderVstestTests 16/16 on both
console axes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Consume the latest MTP client drop from testfx#10297

Picks up the two commits that landed on the testfx branch after the utf8fix1
pack: the header line buffer is now reused across lines instead of allocated per
line (server mode emits a notification per test, so that was a real hot-path
allocation), plus comments recording why Content-Length is intentionally not
capped and why the framing tests are not cross-TFM coverage.

Both changes are to TcpMessageHandler, which compiles into CrossPlatEngine, so
they are verified here rather than assumed. Re-verified from a cleared NuGet
package cache:

- CrossPlatEngine builds clean on net462, netstandard2.0 and net8.0.
- MTP unit tests 142/142 across net11.0 and net481.
- MtpUnderVstestTests 16/16 on both console axes.
- testfx's own ServerClient unit tests 48/48, confirming the shared transport is
  still good on both formatter paths.

The buffer is safe to hold as instance state for the same reason the existing
read offsets are: reads are single-threaded, driven by exactly one read loop.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f

* Consume the published MTP client package; drop the interim local feed

testfx#10085 shipped the source-only MTP server-mode client to the
dnceng-public dotnet-tools feed (already configured in NuGet.config), under
its final name Microsoft.Testing.Platform.ServerMode.Client.Sources. Repin
CrossPlatEngine from the interim local-feed drop
(Microsoft.Testing.Platform.ServerClient.Source 2.4.0-dev.utf8fix2) to the
published 2.4.0-preview.26410.1 and remove the whole interim scaffolding:

- eng/local-mtp-feed and its NuGet.config source + .gitignore exception.
- The GetNugetSourceParameters feed-order workaround in IntegrationTestBuild,
  which only existed to make a local-folder source restore alongside the
  remote https feeds. With no local folder it reverts to the simple base.

The published package compiles its own down-level nullable-annotation
polyfills on net462/netstandard2.0, which collide with the identical set
CrossPlatEngine already imports from CoreUtilities (CS0436). Define
MTP_CLIENT_EXCLUDE_NULLABLE_ATTRIBUTES so the package defers to those; it is
a no-op on net8.0 where the attributes are in-box.

The C# namespace (Microsoft.Testing.Platform.ServerMode.Client) is unchanged,
so the retarget glue and azat's unit tests bind to the published package with
no code change. Restore resolves 2.4.0-preview.26410.1 from the real feed with
no local folder; build is clean on all three TFMs.

🤖

* Enable the MTP testhost in the non-ASCII acceptance test

RunMtpApplicationPreservesNonAsciiTestNames drove the MTP app with a plain
InvokeVsTest, which stopped detecting the app after main merged #16337
(MTP testhost disabled by default). Align it with every other MTP-driving
test by using InvokeVsTestWithMtpTestHostEnabled, so the net11.0 runner
finds the testhost again. net11.0 is back to a full pass; the remaining
net481 /logger:trx failures are the pre-existing environmental logger-load
issue on the desktop runner, unrelated to this change.

🤖

* Reject fractional MTP line numbers

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

* Reject selected MTP nodes without UIDs

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

---------

Co-authored-by: Azat Muzafarov <azatm@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cd0793f4-d530-44b6-b881-fed3be6aa52f
Preserve console logger verbosity from runsettings without dropping MSBuild verbosity for unrelated settings. Make the acceptance assertion portable across ANSI-color implementations.

🤖

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot Bot review requested due to automatic review settings August 14, 2026 21:01

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: Verbosity-Skipping Refinement (2026-08-14)

This commit supersedes the earlier unconditional "skip verbosity when any settings file is present" approach with a targeted check: verbosity is only omitted when the settings file specifically configures the console logger verbosity. That is a meaningful improvement — without it, a settings file that merely sets MaxCpuCount would have silently suppressed verbosity injection, degrading output.

Two-part coordination is correct. HasConsoleLoggerVerbosity = true--logger:Console (no Verbosity=) → LoggerUtilities.AddLoggerToRunSettings with loggerParameters = nulllogger.Configuration stays nullexistingLogger.Configuration (from runsettings) is preserved. The handshake works cleanly.

VSTestTask vs VSTestTask2 split is correctly bounded. VSTestTask2 (MSBuildLogger) always needs MSBuild-derived verbosity and is unaffected by the hasConsoleLoggerVerbosityInRunSettings path.

Exception handling in HasConsoleLoggerVerbosity is appropriateXmlException, IOException, UnauthorizedAccessException are caught and false is returned. File.Exists guards the path before loading, so ArgumentException for invalid path characters won't reach XDocument.Load.

Tests look correct. Assert.DoesNotMatchRegex + Assert.Contains together verify no Verbosity= is injected while the logger itself is still added. The acceptance test's StdOutputContains("PassingTest") + ValidateSummaryStatus(1,1,1) correctly distinguishes normal (prints individual test names) from minimal (does not) verbosity.

One low-severity finding (inline): Descendants() matches <Verbosity> anywhere in the subtree rather than specifically under <Configuration>. Not a practical risk, but a tighter scope would be more precise.


🧠 Reviewed by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

{
if (!File.Exists(settingsFile))
{
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Correctness — minor] HasConsoleLoggerVerbosity uses logger.Descendants() which searches the entire subtree of each <Logger> element for any <Verbosity> node. A <Verbosity> element placed outside <Configuration> — e.g., under a custom vendor extension block — would incorrectly suppress verbosity injection even though the structured <Configuration><Verbosity> path is absent.

The semantically precise check would scope to only <Configuration> children:

foreach (var configuration in logger.Elements())
{
    if (!configuration.Name.LocalName.Equals("Configuration", StringComparison.OrdinalIgnoreCase))
        continue;
    foreach (var element in configuration.Elements())
    {
        if (element.Name.LocalName.Equals("Verbosity", StringComparison.OrdinalIgnoreCase))
            return true;
    }
}

In practice no settings file would put <Verbosity> outside <Configuration>, so this is low risk, but the tighter check would make the intent explicit and match the schema.

🤖

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot Bot review requested due to automatic review settings August 14, 2026 21:23

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: Test Assertion Portability Fix (2026-08-14 21:25)

The only new commit since the previous expert review (21:07Z) is 781b3c0 ("Fix settings argument assertion"), which splits one platform-sensitive assertion:

// Before
Assert.Contains($"--settings:\"{settingsFile}\"", commandline);
// After
Assert.Contains("--settings:", commandline);
Assert.Contains(settingsFile, commandline);

This is correct: the original assertion could fail on platforms where CommandLineBuilder.AppendSwitchIfNotNull produces different quoting. Splitting into two assertions is the standard pattern for cross-platform CLI argument tests in this repo.

No production code changed. The HasConsoleLoggerVerbosity implementation, LoggerUtilities.AddLoggerToRunSettings fix, and acceptance test added in 6e566cb were already reviewed and are sound. The low-severity Descendants() scope finding noted in the previous pass remains open but is not a practical risk given the schema.


🧠 Reviewed by Expert Code Reviewer 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

Jakub Jareš (nohwnd) and others added 2 commits August 15, 2026 01:36
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Assert on the skipped test name, which normal verbosity emits consistently on Windows, Linux, and macOS. Passing test names are not emitted by the MTP path on Unix.

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

Copilot-Session: a96bafe8-bea8-423a-822e-d1a5ab43cb8a

🤖
Copilot Bot review requested due to automatic review settings August 15, 2026 07: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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: LoggerRunSettings verbosity fix

Dimension Status
Correctness of HasConsoleLoggerVerbosity ✅ pass (two nits below)
Correctness of LoggerUtilities config preservation ✅ pass
Correctness of KnownExtensionInstanceFactory pattern ✅ pass
Edge cases / interaction between the two fix parts ✅ correct
Backward compatibility (VSTestTask vs VSTestTask2 split) ✅ correct
Exception handling in XML parsing ⚠️ one nit
XmlNode aliasing in LoggerUtilities ⚠️ cosmetic note

3 findings — all nit/informational, none merge-blocking.

Core logic assessment

The two-part fix composes correctly end-to-end:

  1. HasConsoleLoggerVerbosity reads the settings file and returns true if <Verbosity> is present anywhere under the matched console logger element. When true, TestTaskUtils injects --logger:Console without a Verbosity=X suffix.
  2. LoggerUtilities.AddLoggerToRunSettings then finds the existing logger from the settings file via GetExistingLoggerIndex (OrdinalIgnoreCase on both FriendlyName and URI — consistent with the detection logic), and because logger.Configuration is null (no CLI params), it preserves the settings file's Configuration node. The ConsoleLogger is initialized with the settings-file verbosity intact.

The precedence rule is also correct: if the user explicitly passes --logger:Console;Verbosity=normal (non-null Configuration), the preservation condition is false and the CLI arg wins. That's the right semantics.

The VSTestTask vs VSTestTask2 split at line 54–55 is the right boundary — MSBuildLogger verbosity is MSBuild-derived and shouldn't be suppressed based on runsettings content.

The KnownExtensionInstanceFactory cast (_requestData as RequestData)? is safe (returns null if not RequestData) and not a layering violation — CrossPlatEngine already depends on Common. The dedup via _initializedLoggers.Contains(type) works correctly for both the URI and type-based initialization paths.


🧠 Reviewed by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠


private static bool HasConsoleLoggerVerbosity(string settingsFile)
{
if (!File.Exists(settingsFile))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: File.Exists is redundant here — XDocument.Load already catches IOException (which covers FileNotFoundException, DirectoryNotFoundException, network errors, etc.) and returns false. The File.Exists pre-check introduces a classic TOCTOU window: the file could be deleted, renamed, or its directory unmounted between the check and the load. Since the IOException catch handles all of those cases correctly, the guard can be dropped:

// Before
if (!File.Exists(settingsFile))
    return false;

// After: just rely on the IOException catch below — cleaner and race-free

The File.Exists path also returns false for permission errors and UNC unavailability, which masks the reason the function returned early (the caller only sees "no verbosity configured"). Not a correctness issue in practice, but removing the check simplifies the code.

continue;
}

foreach (var element in logger.Descendants())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[RunSettings Validation] Descendants() traverses the entire subtree under the matched <Logger> element, not just Configuration/Verbosity. A settings file with an unusual (but syntactically valid) structure like:

<Logger friendlyName="console">
  <PluginOptions>
    <Verbosity>x</Verbosity>   <!-- hypothetical third-party config -->
  </PluginOptions>
</Logger>

...would produce a false-positive and suppress verbosity injection. In that case LoggerUtilities preserves the whole Configuration node (which may be null or may not contain a real verbosity value), so the ConsoleLogger silently uses its default.

The canonical schema puts <Verbosity> exactly one level inside <Configuration>:

foreach (var element in logger.Element("Configuration")?.Elements() ?? Enumerable.Empty<XElement>())
{
    if (element.Name.LocalName.Equals("Verbosity", StringComparison.OrdinalIgnoreCase))
        return true;
}

This constrains the match to exactly where the ConsoleLogger expects it and avoids the false-positive. Nit, won't occur with any settings file generated by VS or dotnet test.


// When no new Configuration is being set, preserve the existing logger's Configuration
// so that LoggerRunSettings in .runsettings files are not silently discarded.
if (logger.Configuration is null && existingLogger.Configuration is not null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[RunSettings Validation] When logger.Configuration is null && existingLogger.Configuration is not null, we assign the same XmlNode instance to the incoming logger. That's fine (the existing logger is about to be removed from the list at line 84, so there's only one live reference), but it's worth noting the implicit invariant: if InitializeLogger or anything downstream ever re-parents this node into a different XmlDocument, you'll get an InvalidOperationException ("The node belongs to a different document").

In practice, serialization reads outerXml / InnerXml strings from the node rather than moving it, so this is safe today. Just flagging for future maintainers who add Configuration mutation code.

if (!task.VSTestSetting.IsNullOrEmpty())
{
isRunSettingsEnabled = true;
hasConsoleLoggerVerbosityInRunSettings =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Description] PR scope does not match description

The PR description mentions only a targeted 2-file bug fix for #10369, but the actual diff contains 573 changed files (35,698 additions / 17,339 deletions). Undescribed changes include:

  • New MTP proxy types: MtpProxyDiscoveryManager, MtpProxyExecutionManager, MtpProxyManagerFactory, MtpDataCollectionForwarder, MtpTestNodeConverter
  • Major refactoring of ParallelOperationManager, ProxyDiscoveryManager, ProxyExecutionManager
  • New MicrosoftTestingPlatformDetector utility
  • KnownExtensionInstanceFactory extension point on RequestData / TestLoggerManager
  • IPC/serialization changes in CommunicationUtilities
  • eng/expected-dll-frameworks.json and eng/expected-nupkg-file-counts.json updates

The bug fix for #10369 (TestTaskUtils + LoggerUtilities) is present and correctly implemented — but reviewers relying on the description will be unaware of the bulk of the changes. Please update the description to reflect the full scope, or split the infrastructure work into a separate PR.

🧠 Reviewed by Expert Code Reviewer

The MTP output uses the VSTest summary format on Unix, while ValidateSummaryStatus expects the dotnet test format. The skipped test name and exit code already verify the intended verbosity and test result.

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

Copilot-Session: a96bafe8-bea8-423a-822e-d1a5ab43cb8a

🤖
Copilot Bot review requested due to automatic review settings August 15, 2026 08:17

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review — head commit (2026-08-15)

Test change: ValidateSummaryStatus removal ✅

The deletion is correct. ValidateSummaryStatus parses the summary line for the dotnet test format (e.g. Passed! - ...) but on Unix with MTP the summary uses the VSTest console format instead — so the assertion was inherently platform-specific and not reliably verifiable cross-platform. The regression scenario (verbosity is respected → individual skipped test names appear at normal) is fully covered by StdOutputContains("SkippingTest"), and ExitCodeEquals(1) still confirms the run produced expected failures. Nothing meaningful is lost.

Prior findings status

# Finding Status
1 Descendants() false-positive in HasConsoleLoggerVerbosity ⚠️ still present — inline comment posted
2 File.Exists TOCTOU before try/catch i️ still present, but benign — the race window is real but the worst outcome is a redundant IOException already handled by the catch block; not worth a blocker
3 XmlNode aliasing (logger.Configuration = existingLogger.Configuration) i️ still present, safe today — both point to the same node but the old entry is immediately removed before serialisation

Dimension summary

Dimension Status
Dependency & Package Integrity
Cross-TFM & Framework Resolution
IPC Transport & Protocol Stability
Error Reporting & Diagnostic Clarity
RunSettings Validation & Inference ⚠️ Descendants() scope too broad (see inline)
Backward Compatibility & Rollback Safety
Acceptance Test Coverage Design ✅ — verbosity fix coverage intact post-deletion
All other dimensions ✅ N/A

One minor nit inline. Core fix is correct, test removal is justified. Looks good overall.


🧠 Reviewed by Expert Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

continue;
}

foreach (var element in logger.Descendants())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[RunSettings Validation] logger.Descendants() walks the entire subtree under <Logger>, so a <Verbosity> element nested anywhere (e.g. inside a third-party plugin-specific section) would produce a false positive and cause VSTestTask to silently omit the MSBuild-derived verbosity override. The intent is to check only the <Configuration> child.

Suggested change
foreach (var element in logger.Descendants())
foreach (var element in logger.Element("Configuration")?.Elements() ?? Enumerable.Empty<XElement>())

This also avoids the Descendants overhead on arbitrarily deep trees.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LoggerRunSettings not working to set verbosity

8 participants