[Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

Description

@github-actions

🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

Goal and Rationale

Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

Focus Area

Code-Level Efficiency — unnecessary object creation / allocation.

Changes

1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

// BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

// AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

Savings per test node traversal (tree-filter path):

BeforeAfter
Objects per node1 StringBuilder + 1 string1 string
Allocations eliminated1 StringBuilder per node

For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

Before:

staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

After:

staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

Energy Efficiency Evidence

Proxy metric: heap allocations per test node traversal.

Allocation reduction is the primary proxy for energy efficiency here:

  • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
  • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
  • Per-node allocations scale linearly with test-suite size

Reproducibility:

export PATH="$PWD/.dotnet:$PATH"
dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

Green Software Foundation Context

Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

Trade-offs

  • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
  • The change is a net simplification — the code is shorter and easier to reason about.
  • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

Test Status

Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

Generated by Daily Efficiency Improver · ● 4M ·


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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

Click here to create the pull request

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

Show patch preview (153 of 153 lines)
From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Sat, 25 Apr 2026 04:21:44 +0000
Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
and CreateWrappedName
- Replace StringBuilder in the BFS queue with string, eliminating one
StringBuilder allocation per test node during tree traversal. Each
node's full path is now an immutable string shared directly with
child nodes as their base path.
- Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
filter lookups. This avoids allocating a temporary PlatformTestNodeUid
wrapper object on every node lookup in the HashSet.
- Simplify CreateWrappedName in TestArgumentsManager to a conditional
string.Concat, removing a StringBuilder allocation per argument entry.
Proxy metric: heap allocations per test node during BFS traversal.
Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
per node (filter lookup).
After: 1 string per node (path), no PlatformTestNodeUid per lookup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
.../Engine/TestArgumentsManager.cs | 18 +-------
2 files changed, 24 insertions(+), 37 deletions(-)
diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
index cdf5a26bb..33dd922ad 100644
--- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
using System.Web;
-using Microsoft.Testing.Framework.Helpers;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Requests;
@@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
... (truncated)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

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

      [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

      Description

      @github-actions

      🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

      Goal and Rationale

      Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

      Focus Area

      Code-Level Efficiency — unnecessary object creation / allocation.

      Changes

      1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

      Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

      // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

      After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

      // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

      Savings per test node traversal (tree-filter path):

      BeforeAfter
      Objects per node1 StringBuilder + 1 string1 string
      Allocations eliminated1 StringBuilder per node

      For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

      2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

      Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

      After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

      Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

      3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

      Before:

      staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

      After:

      staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

      Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

      Energy Efficiency Evidence

      Proxy metric: heap allocations per test node traversal.

      Allocation reduction is the primary proxy for energy efficiency here:

      • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
      • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
      • Per-node allocations scale linearly with test-suite size

      Reproducibility:

      export PATH="$PWD/.dotnet:$PATH"
      dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
      dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

      To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

      Green Software Foundation Context

      Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

      Trade-offs

      • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
      • The change is a net simplification — the code is shorter and easier to reason about.
      • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

      Test Status

      Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
      Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

      Generated by Daily Efficiency Improver · ● 4M ·


      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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

      Click here to create the pull request

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

      Show patch preview (153 of 153 lines)
      From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
      From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
      Date: Sat, 25 Apr 2026 04:21:44 +0000
      Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
      and CreateWrappedName
      - Replace StringBuilder in the BFS queue with string, eliminating one
      StringBuilder allocation per test node during tree traversal. Each
      node's full path is now an immutable string shared directly with
      child nodes as their base path.
      - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
      filter lookups. This avoids allocating a temporary PlatformTestNodeUid
      wrapper object on every node lookup in the HashSet.
      - Simplify CreateWrappedName in TestArgumentsManager to a conditional
      string.Concat, removing a StringBuilder allocation per argument entry.
      Proxy metric: heap allocations per test node during BFS traversal.
      Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
      per node (filter lookup).
      After: 1 string per node (path), no PlatformTestNodeUid per lookup.
      Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
      ---
      .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
      .../Engine/TestArgumentsManager.cs | 18 +-------
      2 files changed, 24 insertions(+), 37 deletions(-)
      diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
      index cdf5a26bb..33dd922ad 100644
      --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
      using System.Web;
      -using Microsoft.Testing.Framework.Helpers;
      using Microsoft.Testing.Platform.Extensions.Messages;
      using Microsoft.Testing.Platform.Requests;
      @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
      public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
      ... (truncated)

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

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

          [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

          Description

          @github-actions

          🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

          Goal and Rationale

          Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

          Focus Area

          Code-Level Efficiency — unnecessary object creation / allocation.

          Changes

          1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

          Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

          // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

          After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

          // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

          Savings per test node traversal (tree-filter path):

          BeforeAfter
          Objects per node1 StringBuilder + 1 string1 string
          Allocations eliminated1 StringBuilder per node

          For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

          2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

          Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

          After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

          Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

          3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

          Before:

          staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

          After:

          staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

          Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

          Energy Efficiency Evidence

          Proxy metric: heap allocations per test node traversal.

          Allocation reduction is the primary proxy for energy efficiency here:

          • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
          • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
          • Per-node allocations scale linearly with test-suite size

          Reproducibility:

          export PATH="$PWD/.dotnet:$PATH"
          dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
          dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

          To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

          Green Software Foundation Context

          Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

          Trade-offs

          • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
          • The change is a net simplification — the code is shorter and easier to reason about.
          • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

          Test Status

          Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
          Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

          Generated by Daily Efficiency Improver · ● 4M ·


          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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

          Click here to create the pull request

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

          Show patch preview (153 of 153 lines)
          From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
          From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
          Date: Sat, 25 Apr 2026 04:21:44 +0000
          Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
          and CreateWrappedName
          - Replace StringBuilder in the BFS queue with string, eliminating one
          StringBuilder allocation per test node during tree traversal. Each
          node's full path is now an immutable string shared directly with
          child nodes as their base path.
          - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
          filter lookups. This avoids allocating a temporary PlatformTestNodeUid
          wrapper object on every node lookup in the HashSet.
          - Simplify CreateWrappedName in TestArgumentsManager to a conditional
          string.Concat, removing a StringBuilder allocation per argument entry.
          Proxy metric: heap allocations per test node during BFS traversal.
          Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
          per node (filter lookup).
          After: 1 string per node (path), no PlatformTestNodeUid per lookup.
          Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
          ---
          .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
          .../Engine/TestArgumentsManager.cs | 18 +-------
          2 files changed, 24 insertions(+), 37 deletions(-)
          diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
          index cdf5a26bb..33dd922ad 100644
          --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
          using System.Web;
          -using Microsoft.Testing.Framework.Helpers;
          using Microsoft.Testing.Platform.Extensions.Messages;
          using Microsoft.Testing.Platform.Requests;
          @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
          public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
          ... (truncated)

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

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

              [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

              Description

              @github-actions

              🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

              Goal and Rationale

              Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

              Focus Area

              Code-Level Efficiency — unnecessary object creation / allocation.

              Changes

              1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

              Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

              // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

              After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

              // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

              Savings per test node traversal (tree-filter path):

              BeforeAfter
              Objects per node1 StringBuilder + 1 string1 string
              Allocations eliminated1 StringBuilder per node

              For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

              2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

              Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

              After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

              Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

              3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

              Before:

              staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

              After:

              staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

              Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

              Energy Efficiency Evidence

              Proxy metric: heap allocations per test node traversal.

              Allocation reduction is the primary proxy for energy efficiency here:

              • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
              • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
              • Per-node allocations scale linearly with test-suite size

              Reproducibility:

              export PATH="$PWD/.dotnet:$PATH"
              dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
              dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

              To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

              Green Software Foundation Context

              Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

              Trade-offs

              • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
              • The change is a net simplification — the code is shorter and easier to reason about.
              • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

              Test Status

              Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
              Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

              Generated by Daily Efficiency Improver · ● 4M ·


              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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

              Click here to create the pull request

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

              Show patch preview (153 of 153 lines)
              From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
              From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
              Date: Sat, 25 Apr 2026 04:21:44 +0000
              Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
              and CreateWrappedName
              - Replace StringBuilder in the BFS queue with string, eliminating one
              StringBuilder allocation per test node during tree traversal. Each
              node's full path is now an immutable string shared directly with
              child nodes as their base path.
              - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
              filter lookups. This avoids allocating a temporary PlatformTestNodeUid
              wrapper object on every node lookup in the HashSet.
              - Simplify CreateWrappedName in TestArgumentsManager to a conditional
              string.Concat, removing a StringBuilder allocation per argument entry.
              Proxy metric: heap allocations per test node during BFS traversal.
              Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
              per node (filter lookup).
              After: 1 string per node (path), no PlatformTestNodeUid per lookup.
              Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
              ---
              .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
              .../Engine/TestArgumentsManager.cs | 18 +-------
              2 files changed, 24 insertions(+), 37 deletions(-)
              diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
              index cdf5a26bb..33dd922ad 100644
              --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
              using System.Web;
              -using Microsoft.Testing.Framework.Helpers;
              using Microsoft.Testing.Platform.Extensions.Messages;
              using Microsoft.Testing.Platform.Requests;
              @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
              public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
              ... (truncated)

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

                  Description

                  @github-actions

                  🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

                  Goal and Rationale

                  Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

                  Focus Area

                  Code-Level Efficiency — unnecessary object creation / allocation.

                  Changes

                  1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

                  Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

                  // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

                  After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

                  // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

                  Savings per test node traversal (tree-filter path):

                  BeforeAfter
                  Objects per node1 StringBuilder + 1 string1 string
                  Allocations eliminated1 StringBuilder per node

                  For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

                  2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

                  Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

                  After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

                  Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

                  3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

                  Before:

                  staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

                  After:

                  staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

                  Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

                  Energy Efficiency Evidence

                  Proxy metric: heap allocations per test node traversal.

                  Allocation reduction is the primary proxy for energy efficiency here:

                  • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
                  • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
                  • Per-node allocations scale linearly with test-suite size

                  Reproducibility:

                  export PATH="$PWD/.dotnet:$PATH"
                  dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
                  dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

                  To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

                  Green Software Foundation Context

                  Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

                  Trade-offs

                  • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
                  • The change is a net simplification — the code is shorter and easier to reason about.
                  • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

                  Test Status

                  Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
                  Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

                  Generated by Daily Efficiency Improver · ● 4M ·


                  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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

                  Click here to create the pull request

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

                  Show patch preview (153 of 153 lines)
                  From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
                  From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
                  Date: Sat, 25 Apr 2026 04:21:44 +0000
                  Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
                  and CreateWrappedName
                  - Replace StringBuilder in the BFS queue with string, eliminating one
                  StringBuilder allocation per test node during tree traversal. Each
                  node's full path is now an immutable string shared directly with
                  child nodes as their base path.
                  - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
                  filter lookups. This avoids allocating a temporary PlatformTestNodeUid
                  wrapper object on every node lookup in the HashSet.
                  - Simplify CreateWrappedName in TestArgumentsManager to a conditional
                  string.Concat, removing a StringBuilder allocation per argument entry.
                  Proxy metric: heap allocations per test node during BFS traversal.
                  Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
                  per node (filter lookup).
                  After: 1 string per node (path), no PlatformTestNodeUid per lookup.
                  Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
                  ---
                  .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
                  .../Engine/TestArgumentsManager.cs | 18 +-------
                  2 files changed, 24 insertions(+), 37 deletions(-)
                  diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
                  index cdf5a26bb..33dd922ad 100644
                  --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
                  using System.Web;
                  -using Microsoft.Testing.Framework.Helpers;
                  using Microsoft.Testing.Platform.Extensions.Messages;
                  using Microsoft.Testing.Platform.Requests;
                  @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
                  public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
                  ... (truncated)

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

                      Description

                      @github-actions

                      🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

                      Goal and Rationale

                      Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

                      Focus Area

                      Code-Level Efficiency — unnecessary object creation / allocation.

                      Changes

                      1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

                      Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

                      // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

                      After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

                      // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

                      Savings per test node traversal (tree-filter path):

                      BeforeAfter
                      Objects per node1 StringBuilder + 1 string1 string
                      Allocations eliminated1 StringBuilder per node

                      For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

                      2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

                      Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

                      After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

                      Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

                      3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

                      Before:

                      staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

                      After:

                      staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

                      Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

                      Energy Efficiency Evidence

                      Proxy metric: heap allocations per test node traversal.

                      Allocation reduction is the primary proxy for energy efficiency here:

                      • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
                      • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
                      • Per-node allocations scale linearly with test-suite size

                      Reproducibility:

                      export PATH="$PWD/.dotnet:$PATH"
                      dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
                      dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

                      To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

                      Green Software Foundation Context

                      Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

                      Trade-offs

                      • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
                      • The change is a net simplification — the code is shorter and easier to reason about.
                      • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

                      Test Status

                      Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
                      Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

                      Generated by Daily Efficiency Improver · ● 4M ·


                      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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

                      Click here to create the pull request

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

                      Show patch preview (153 of 153 lines)
                      From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
                      From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
                      Date: Sat, 25 Apr 2026 04:21:44 +0000
                      Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
                      and CreateWrappedName
                      - Replace StringBuilder in the BFS queue with string, eliminating one
                      StringBuilder allocation per test node during tree traversal. Each
                      node's full path is now an immutable string shared directly with
                      child nodes as their base path.
                      - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
                      filter lookups. This avoids allocating a temporary PlatformTestNodeUid
                      wrapper object on every node lookup in the HashSet.
                      - Simplify CreateWrappedName in TestArgumentsManager to a conditional
                      string.Concat, removing a StringBuilder allocation per argument entry.
                      Proxy metric: heap allocations per test node during BFS traversal.
                      Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
                      per node (filter lookup).
                      After: 1 string per node (path), no PlatformTestNodeUid per lookup.
                      Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
                      ---
                      .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
                      .../Engine/TestArgumentsManager.cs | 18 +-------
                      2 files changed, 24 insertions(+), 37 deletions(-)
                      diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
                      index cdf5a26bb..33dd922ad 100644
                      --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
                      using System.Web;
                      -using Microsoft.Testing.Framework.Helpers;
                      using Microsoft.Testing.Platform.Extensions.Messages;
                      using Microsoft.Testing.Platform.Requests;
                      @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
                      public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
                      ... (truncated)

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

                          Description

                          @github-actions

                          🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

                          Goal and Rationale

                          Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

                          Focus Area

                          Code-Level Efficiency — unnecessary object creation / allocation.

                          Changes

                          1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

                          Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

                          // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

                          After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

                          // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

                          Savings per test node traversal (tree-filter path):

                          BeforeAfter
                          Objects per node1 StringBuilder + 1 string1 string
                          Allocations eliminated1 StringBuilder per node

                          For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

                          2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

                          Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

                          After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

                          Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

                          3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

                          Before:

                          staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

                          After:

                          staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

                          Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

                          Energy Efficiency Evidence

                          Proxy metric: heap allocations per test node traversal.

                          Allocation reduction is the primary proxy for energy efficiency here:

                          • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
                          • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
                          • Per-node allocations scale linearly with test-suite size

                          Reproducibility:

                          export PATH="$PWD/.dotnet:$PATH"
                          dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
                          dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

                          To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

                          Green Software Foundation Context

                          Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

                          Trade-offs

                          • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
                          • The change is a net simplification — the code is shorter and easier to reason about.
                          • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

                          Test Status

                          Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
                          Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

                          Generated by Daily Efficiency Improver · ● 4M ·


                          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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

                          Click here to create the pull request

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

                          Show patch preview (153 of 153 lines)
                          From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
                          From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
                          Date: Sat, 25 Apr 2026 04:21:44 +0000
                          Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
                          and CreateWrappedName
                          - Replace StringBuilder in the BFS queue with string, eliminating one
                          StringBuilder allocation per test node during tree traversal. Each
                          node's full path is now an immutable string shared directly with
                          child nodes as their base path.
                          - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
                          filter lookups. This avoids allocating a temporary PlatformTestNodeUid
                          wrapper object on every node lookup in the HashSet.
                          - Simplify CreateWrappedName in TestArgumentsManager to a conditional
                          string.Concat, removing a StringBuilder allocation per argument entry.
                          Proxy metric: heap allocations per test node during BFS traversal.
                          Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
                          per node (filter lookup).
                          After: 1 string per node (path), no PlatformTestNodeUid per lookup.
                          Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
                          ---
                          .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
                          .../Engine/TestArgumentsManager.cs | 18 +-------
                          2 files changed, 24 insertions(+), 37 deletions(-)
                          diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
                          index cdf5a26bb..33dd922ad 100644
                          --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
                          using System.Web;
                          -using Microsoft.Testing.Framework.Helpers;
                          using Microsoft.Testing.Platform.Extensions.Messages;
                          using Microsoft.Testing.Platform.Requests;
                          @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
                          public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
                          ... (truncated)

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              [Efficiency Improver] perf: reduce allocations in BFSTestNodeVisitor path tracking and WrappedName building #7828

                              Description

                              @github-actions

                              🤖 This is an automated pull request from Daily Efficiency Improver, an AI assistant focused on reducing the energy consumption and computational footprint of this repository.

                              Goal and Rationale

                              Eliminate unnecessary heap allocations in the BFS test-node traversal loop (BFSTestNodeVisitor.VisitAsync) and the CreateWrappedName helper. These code paths run for every test node during discovery and execution, so allocation savings compound linearly with test-suite size.

                              Focus Area

                              Code-Level Efficiency — unnecessary object creation / allocation.

                              Changes

                              1. BFSTestNodeVisitor — replace StringBuilder with string in BFS queue

                              Before: Each queued tuple held a StringBuilder representing the parent's full path. On dequeue, a newStringBuilder was allocated to copy the parent path, the separator and the encoded node name were appended, then ToString() was called.

                              // BeforeQueue<(TestNode,TestNodeUid?,StringBuilderNodeFullPath)>queue=new();queue.Enqueue((node,null,new()));// one empty StringBuilder per root node// ...StringBuildernodeFullPathForChildren=newStringBuilder().Append(nodeFullPath);// copy per nodenodeFullPathForChildren.Append('/');nodeFullPathForChildren.Append(EncodeString(...));stringcurrentNodeFullPath=nodeFullPathForChildren.ToString();// one string per node

                              After: The queue holds string directly. Child nodes receive an already-materialised string as their base path.

                              // AfterQueue<(TestNode,TestNodeUid?,stringNodeFullPath)>queue=new();queue.Enqueue((node,null,string.Empty));// zero extra allocations per root node// ...stringcurrentNodeFullPath=nodeFullPath.Length==0||nodeFullPath[^1]!=TreeNodeFilter.PathSeparator?string.Concat(nodeFullPath,"/",encodedName)// one string.Concat per node:string.Concat(nodeFullPath,encodedName);

                              Savings per test node traversal (tree-filter path):

                              BeforeAfter
                              Objects per node1 StringBuilder + 1 string1 string
                              Allocations eliminated1 StringBuilder per node

                              For a test suite of n nodes: n fewer object allocations, plus the associated internal char[] buffer inside each StringBuilder.

                              2. BFSTestNodeVisitor — replace HashSet<PlatformTestNodeUid> with HashSet<string>

                              Before: The UID filter set was HashSet<PlatformTestNodeUid>. Every lookup called currentNode.StableUid.ToPlatformTestNodeUid(), allocating a new PlatformTestNodeUid wrapper object on every node just to check membership.

                              After: The filter set is HashSet<string> populated with uid.Value strings. Lookups use currentNode.StableUid.Value directly — no wrapper allocation.

                              Savings: 1 PlatformTestNodeUid allocation per node in UID-filter mode (re-run / filtered runs).

                              3. TestArgumentsManager — simplify CreateWrappedName to avoid StringBuilder

                              Before:

                              staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis){StringBuilderdisplayNameBuilder=new();if(shouldWrapInParenthesis)displayNameBuilder.Append('(');displayNameBuilder.Append(name);if(shouldWrapInParenthesis)displayNameBuilder.Append(')');returndisplayNameBuilder.ToString();}

                              After:

                              staticstringCreateWrappedName(stringname,boolshouldWrapInParenthesis)=>shouldWrapInParenthesis?string.Concat("(",name,")"):name;

                              Savings: 1 StringBuilder allocation per argument entry (data-driven tests), replaced by a single string.Concat call (1 allocation) or no allocation at all when wrapping is not needed.

                              Energy Efficiency Evidence

                              Proxy metric: heap allocations per test node traversal.

                              Allocation reduction is the primary proxy for energy efficiency here:

                              • Fewer heap allocations → less GC pressure → fewer GC pause cycles → lower CPU usage
                              • StringBuilder objects include an internal char[] buffer allocation (typically 16+ chars minimum), not just the object header
                              • Per-node allocations scale linearly with test-suite size

                              Reproducibility:

                              export PATH="$PWD/.dotnet:$PATH"
                              dotnet build src/Adapter/MSTest.Engine/MSTest.Engine.csproj -p:TargetFramework=net8.0 --no-restore
                              dotnet test test/UnitTests/MSTest.Engine.UnitTests/MSTest.Engine.UnitTests.csproj -p:TargetFramework=net8.0 --no-restore --filter "FullyQualifiedName~BFSTestNodeVisitor"

                              To measure allocations before/after, use dotnet-trace with the gc-collect profile or BenchmarkDotNet with [MemoryDiagnoser] on a large synthetic test tree.

                              Green Software Foundation Context

                              Per the Hardware Efficiency principle (use as little physical resources as possible): reducing per-node GC pressure means the GC threads can spend less energy on collection cycles, and DRAM refresh energy is reduced by lowering the live heap size.

                              Trade-offs

                              • string.Concat for path building creates one string per level in the tree rather than incrementally building with StringBuilder. For very deep trees (depth > ~100), this is equivalent in total character copies. For typical test trees (depth ≤ 10), the savings clearly dominate.
                              • The change is a net simplification — the code is shorter and easier to reason about.
                              • No behavioural changes; all existing BFSTestNodeVisitorTests pass.

                              Test Status

                              Build: ✅ MSTest.Engine builds cleanly (0 warnings, 0 errors) on net8.0
                              Tests: ✅ All 10 BFSTestNodeVisitorTests pass on net8.0

                              Generated by Daily Efficiency Improver · ● 4M ·


                              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 efficiency/bfstestnode-string-path-tracking-f474c342a1a32973.

                              Click here to create the pull request

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

                              Show patch preview (153 of 153 lines)
                              From b13f883d4e7b531c5222611ac13a36b86e7e74f2 Mon Sep 17 00:00:00 2001
                              From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
                              Date: Sat, 25 Apr 2026 04:21:44 +0000
                              Subject: [PATCH] perf: reduce allocations in BFSTestNodeVisitor path tracking
                              and CreateWrappedName
                              - Replace StringBuilder in the BFS queue with string, eliminating one
                              StringBuilder allocation per test node during tree traversal. Each
                              node's full path is now an immutable string shared directly with
                              child nodes as their base path.
                              - Replace HashSet<PlatformTestNodeUid> with HashSet<string> for UID
                              filter lookups. This avoids allocating a temporary PlatformTestNodeUid
                              wrapper object on every node lookup in the HashSet.
                              - Simplify CreateWrappedName in TestArgumentsManager to a conditional
                              string.Concat, removing a StringBuilder allocation per argument entry.
                              Proxy metric: heap allocations per test node during BFS traversal.
                              Before: 1 StringBuilder + 1 string per node (path) + 1 PlatformTestNodeUid
                              per node (filter lookup).
                              After: 1 string per node (path), no PlatformTestNodeUid per lookup.
                              Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
                              ---
                              .../Engine/BFSTestNodeVisitor.cs | 43 ++++++++++---------
                              .../Engine/TestArgumentsManager.cs | 18 +-------
                              2 files changed, 24 insertions(+), 37 deletions(-)
                              diff --git a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs
                              index cdf5a26bb..33dd922ad 100644
                              --- a/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs+++ b/src/Adapter/MSTest.Engine/Engine/BFSTestNodeVisitor.cs@@ -3,7 +3,6 @@
                              using System.Web;
                              -using Microsoft.Testing.Framework.Helpers;
                              using Microsoft.Testing.Platform.Extensions.Messages;
                              using Microsoft.Testing.Platform.Requests;
                              @@ -34,22 +33,32 @@ public BFSTestNodeVisitor(IEnumerable<TestNode> rootTestNodes, ITestExecutionFil
                              public async Task VisitAsync(Func<TestNode, TestNodeUid?, Task
                              ... (truncated)

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                area/agentic-workflowsGitHub agentic workflow definitions under .github/workflows/*.md.area/performanceRuntime / build performance / efficiency.type/automationCreated or maintained by an agentic workflow.

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions