Skip to content

[Perf Improver] perf: reduce allocations in test discovery and data-driven test executionΒ #7815

@github-actions

Description

@github-actions

πŸ€– This PR was created by Perf Improver, an automated AI assistant focused on performance improvements.

Goal and Rationale

Eliminate unnecessary heap allocations in two code paths:

  1. ValidSourceExtensions (TestSourceHandler) β€” The property previously allocated a new List<string> on every access. It is called in AreValidSources() during test discovery for each source file. Since the list contents are compile-time constants, it can be safely cached as a static readonly field.

  2. ReflectionTestMethodInfo (TestMethodRunner) β€” ExecuteTestWithDataSourceAsync could allocate up to two ReflectionTestMethodInfo instances per data row when testDataSource is not null and GetDisplayName returns null (requiring fallback to ComputeDefaultDisplayName). The instance is now created once and reused.

Approach

  • Cache ValidSourceExtensions as a private static readonly field. All preprocessor branches (NETFRAMEWORK, WINDOWS_UWP/WIN_UI, default) are compile-time-constant, so the collection is always fixed for a given build target.
  • Refactor the null-coalescing display name resolution in ExecuteTestWithDataSourceAsync to a short-circuit if/else block that creates ReflectionTestMethodInfo at most once.

Performance Evidence

Change 1 β€” ValidSourceExtensions:
Before: every call to testSourceHandler.ValidSourceExtensions allocates a new List<string> (2–3 items).
After: zero allocations per call; returns the same cached collection.
Impact: called once per source file in AreValidSources() during discovery init.

Change 2 β€” ReflectionTestMethodInfo in data-driven tests:
Before: up to 2 ReflectionTestMethodInfo allocations per data row when GetDisplayName returns null.
After: at most 1 allocation per data row (only when testDataSource is not null). Zero allocations when testDataSource is null or displayNameFromTestDataRow is set.
Impact: proportional to number of data rows in data-driven tests.

Trade-offs

  • The cached ValidSourceExtensions collection is an IEnumerable<string> backed by an array literal; it is read-only by nature. No risk of mutation.
  • The refactored display name logic is semantically equivalent to the original null-coalescing chain; the precedence (displayNameFromTestDataRow β†’ GetDisplayName β†’ ComputeDefaultDisplayName β†’ fallback) is preserved.

Reproducibility

Build the affected project and run unit tests:

export PATH="$PWD/.dotnet:$PATH"
dotnet build src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj -f net8.0

For allocation profiling, use dotnet-trace or BenchmarkDotNet with MemoryDiagnoser on a data-driven test suite.

Test Status

MSTestAdapter.PlatformServices builds cleanly with 0 warnings and 0 errors on net8.0.
The MSTest.TestAdapter project has a pre-existing infrastructure build failure (missing ApplicationInsights NuGet) unrelated to these changes.

Note

πŸ”’ Integrity filter blocked 3 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #2340 search_issues: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #757 search_issues: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #3759 search_issues: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Daily Perf Improver Β· ● 6.6M Β· β—·


Note

This was originally intended as a pull request, but GitHub Actions is not permitted to create or approve pull requests in this repository.
The changes have been pushed to branch perf-assist/reduce-allocations-discovery-execution-906516a4f3ae9159.

Click here to create the pull request

To fix the permissions issue, go to Settings β†’ Actions β†’ General and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ

Show patch preview (92 of 92 lines)
From d3ffe3262dee683d79a02a68112bb423aa43ac4b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 24 Apr 2026 13:30:28 +0000
Subject: [PATCH] perf: reduce allocations in test discovery and data-driven
 test execution

- ValidSourceExtensions in TestSourceHandler now returns a cached static
  readonly collection instead of allocating a new List<string> on every
  property access. This eliminates unnecessary heap allocations during
  test source validation.

- ExecuteTestWithDataSourceAsync in TestMethodRunner no longer creates
  two ReflectionTestMethodInfo instances when testDataSource is not null
  and GetDisplayName returns null (requiring fallback to
  ComputeDefaultDisplayName). The instance is now created once and reused,
  halving the allocations for this code path in data-driven tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../Execution/TestMethodRunner.cs             | 17 ++++++++++----
 .../Services/TestSourceHandler.cs             | 23 +++++++++++--------
 2 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
index da74d5b..1438962 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs
@@ -321,10 +321,19 @@ private async Task<TestResult[]> ExecuteTestWithDataSourceAsync(UTF.ITestDataSou
             data = tupleExpandedToArray;
         }
 
-        displayName = displayNameFromTestDataRow
-            ?? testDataSource?.GetDisplayName(new ReflectionTestMethodInfo(_testMethodInfo.MethodInfo, _test.DisplayName), data)
-            ?? (testDataSource is null ? displayName : TestDataSourceUtilities.ComputeDefaultDisplayName(new ReflectionTestMethodInfo(_testMethodInfo.MethodInfo, _test.DisplayName), d
... (truncated)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions