Skip to content

perf: eliminate eager testFullName allocation in MSTestTestNodeConverter#9824

Open
Evangelink wants to merge 1 commit into
mainfrom
dev/amauryleve/friendly-couscous
Open

perf: eliminate eager testFullName allocation in MSTestTestNodeConverter#9824
Evangelink wants to merge 1 commit into
mainfrom
dev/amauryleve/friendly-couscous

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Fixes #9823

Summary

MSTestTestNodeConverter.CreateBaseTestNode eagerly computed string testFullName = $"{testMethod.FullClassName}.{testMethod.Name}"; on every call, but the string was only used as a final null-coalescing fallback for DisplayName. Because TestMethod.DisplayName is always non-null (the constructor sets it to displayName ?? name), this allocation was thrown away on every call — 3× per test per run (discovery, in-progress, result).

AddTrxResultProperties had the same variable for two additional dead uses.

Changes

  • CreateBaseTestNode: inline testFullName into the null-coalescing chain so the interpolation is only evaluated when both prior operands are null (they never are).
  • AddTrxResultProperties: use testMethod.DisplayName directly for TrxTestDefinitionName; use testMethod.FullClassName directly in the TestMethodIdentifierProperty-absent fallback path (it is the source of truth for the type name, avoiding a re-parse of a string that already concatenated FullClassName); remove the now-dead TryParseFullyQualifiedType private method.

Performance

For a 10,000-test suite this eliminates ~30,000 transient string allocations per run in CreateBaseTestNode, plus one string allocation and the associated parse work per TRX-result node in the fallback path.

Trade-offs

None. DisplayName is documented as always initialized and FullClassName is the source of truth for the type name, so behavior is identical.

Test status

MSTest.TestAdapter builds cleanly (0 warnings, 0 errors). All 29 MSTestTestNodeConverter unit tests pass (net9.0).

In CreateBaseTestNode, testFullName was computed eagerly even though it is
only needed as a final fallback when both displayNameOverride and
TestMethod.DisplayName are null. DisplayName is always initialized, so the
allocation was never used. Inline the interpolation so it is only evaluated
if the earlier operands are null.

In AddTrxResultProperties, use testMethod.DisplayName directly for
TrxTestDefinitionName and testMethod.FullClassName directly in the
TestMethodIdentifierProperty-absent fallback, removing the now-dead
TryParseFullyQualifiedType helper.

Fixes #9823

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 10, 2026 14:46
@Evangelink Evangelink enabled auto-merge (squash) July 10, 2026 14:49
@Evangelink Evangelink added the state/needs-review Awaiting review from the team. label Jul 10, 2026

@github-actions github-actions Bot 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.

Note

🤖 Automated review by GitHub Copilot. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.

Review Summary

Clean performance optimization — no issues found across applicable dimensions.

Dimension Verdict
1. Algorithmic Correctness ✅ Pass
2. Performance ✅ Pass (this is the improvement)
3. Public API N/A (no public API changes)
4. Backward Compatibility ✅ Pass — behavior is identical
5–22. Remaining dimensions ✅ Pass / N/A

Key observations

  1. Correctness of FullClassName substitution: The old TryParseFullyQualifiedType reconstructed FullClassName.Name then parsed back out the part before the last dot — which is just FullClassName. Using FullClassName directly is both simpler and more correct (e.g., if Name contained a . from data-driven parameters, the old parser could produce wrong results).

  2. Dead-code removal is safe: TryParseFullyQualifiedType had no other callers, so removing it has no impact.

  3. Null-coalescing fallback: The PR correctly notes DisplayName is always non-null per the TestMethod constructor, so the interpolated string is never evaluated in practice. The fallback is retained as a defensive measure, which is appropriate.

No action items — LGTM.

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 optimizes the MSTest adapter’s MSTestTestNodeConverter by eliminating an eager testFullName string interpolation that was effectively always unused, reducing transient allocations during test discovery/execution/result reporting.

Changes:

  • In CreateBaseTestNode, removed the eagerly-allocated testFullName local and made the interpolation lazy (only evaluated if earlier operands are null).
  • In AddTrxResultProperties, removed dead uses of testFullName and simplified the fallback path by using testMethod.FullClassName directly.
  • Deleted the now-unused TryParseFullyQualifiedType helper.
Show a summary per file
File Description
src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MSTestTestNodeConverter.cs Removes eager testFullName allocation and simplifies TRX property population to avoid redundant work.

Review details

  • Files reviewed: 1/1 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment on lines +200 to +201
// TestMethod.DisplayName is always initialized (constructor sets it to displayName ?? name).
testNode.Properties.Add(new TrxTestDefinitionName(testMethod.DisplayName ?? $"{testMethod.FullClassName}.{testMethod.Name}"));
Comment on lines +93 to +95
// TestMethod.DisplayName is always initialized (the constructor sets it to displayName ?? name), so the
// interpolated fallback is only evaluated (and only allocates) in the theoretically-null case.
DisplayName = displayNameOverride ?? testMethod.DisplayName ?? $"{testMethod.FullClassName}.{testMethod.Name}",
else
{
throw new InvalidOperationException("Unable to parse fully qualified type name from test: " + testFullName);
throw new InvalidOperationException("Unable to parse fully qualified type name from test: " + $"{testMethod.FullClassName}.{testMethod.Name}");
@github-actions

Copy link
Copy Markdown
Contributor

🔴 Build Failure Analysis

Build status: FAILED (249.9s) — 5 errors, 1 warning

Root Cause

All 4 compilation errors are CA1416 (platform compatibility) in FileLoggerTests.cs:

Lines Class Issue
465 TimestampedTask Calls ITask.RunLongRunning() which is [UnsupportedOSPlatform("browser")]
490 NeverCompletingTask Same

The ITask.RunLongRunning method was annotated with [UnsupportedOSPlatform("browser")] (in ITask.cs), but the two test helper classes that delegate to it don't suppress the analyzer warning — triggering CA1416 as errors-on-warnings is enabled.

Fix

Add #pragma warning disable CA1416 / restore around the two RunLongRunning delegation lines, matching the existing pattern already used elsewhere in this file (e.g., for ManualResetEventSlim.Wait and Task.WaitAll).

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 97.7 AIC · ⌖ 5.83 AIC · ⊞ 7.3K · [◷]( · )

@github-actions github-actions Bot 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.

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · 97.7 AIC · ⌖ 5.83 AIC · ⊞ 7.3K ·

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

Labels

state/needs-review Awaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[perf-improver] perf: eliminate eager testFullName allocation in MSTestTestNodeConverter

3 participants