[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink
, '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: eliminate yield-iterator allocations in test execution hot path - #8093

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6
May 13, 2026
Merged

[Efficiency Improver] perf: eliminate yield-iterator allocations in test execution hot path#8093
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6

Conversation

@Evangelink

@EvangelinkAmaury Levé (Evangelink) commented May 11, 2026

Copy link
Copy Markdown
Member

✨ Enhancement

What does this improve?
This PR reduces allocations in MSTest adapter test-execution hot paths while preserving behavior:

  • RetryBaseAttribute discovery now iterates cached method attributes directly instead of using a yield iterator path.
  • DataSourceAttribute handling now uses a direct cached-attribute scan that remains allocation-free and preserves prior cardinality semantics (execute only when exactly one DataSourceAttribute is present).

Why is this valuable?
These paths run during test execution, so avoiding iterator/array allocations reduces overhead in common runtime scenarios without changing intended outcomes.

Implementation approach:

  • Replaced allocation-heavy attribute enumeration patterns with direct iteration over ReflectHelper.Instance.GetCustomAttributesCached(...).
  • Kept/clarified guard behavior for multiple retry attributes.
  • Added unit-test coverage for the multiple-RetryBaseAttribute error path (ThrowMultipleAttributesException) to lock in behavior.
  • Applied minor naming/comment clarity improvements related to the refactor.

Testing

  • Added targeted unit coverage for the retry multi-attribute exception path.
  • Targeted adapter unit test runs were attempted, but restore/test execution is currently blocked in this environment by transient package feed download failures.

github-actionsBotand others added 3 commits May 11, 2026 10:40
AwesomeAssertions was bumped from 9.3.0 to 9.4.0 (PR #8008) and is the
mandated assertion library for this project's test suites. Adding a
glossary entry to explain its purpose and relationship to FluentAssertions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four new terms identified in the 7-day full scan:
- DynamicData: MSTest attribute for data-driven tests (RFC 006;
highlighted by PR #7925 log-accumulation bug fix)
- Lean–C# Correspondence (FV): new FV artifact introduced by PR #7936
FV docs validation workflow
- PropertyBag: core MTP extension API for typed test node metadata
(PR #7940 TestNodeProperties refactor brought these types into focus)
- TestNode: core MTP class representing a discovered/executed test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetRetryAttribute() called GetAttributes<RetryBaseAttribute>() which allocates
a compiler-generated state machine (IEnumerator<T> via yield return) on every
test method construction. Replace with direct iteration of the cached Attribute[]
from GetCustomAttributesCached(). Preserves duplicate-attribute detection and
the custom TypeInspectionException.
TryExecuteDataSourceBasedTestsAsync() called _testMethodInfo.GetAttributes<DataSourceAttribute>()
which allocates a yield iterator state machine AND a DataSourceAttribute[] array.
Replace with ReflectHelper.Instance.IsAttributeDefined<DataSourceAttribute>(),
which iterates the same cached array without any allocation. Since DataSourceAttribute
is sealed and does not allow multiple, presence == exactly one instance.
Proxy metric: heap allocation count
- GetRetryAttribute: 1 allocation eliminated per TestMethodInfo construction
- TryExecuteDataSourceBasedTestsAsync: 2 allocations eliminated per test execution
(for tests without a DataSourceAttribute -- the common case)
- Estimated: ~480 KB GC pressure reduction per 10,000-test run
GSF principle: Hardware Efficiency -- fewer allocations = less DRAM and GC CPU
per joule; Energy Proportionality -- GC cost scales with allocation rate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 09:04

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets runtime efficiency in the MSTest adapter execution hot path by removing allocation-heavy iterator/array creation when checking for certain attributes, and also expands the project glossary with several new terms.

Changes:

  • Updated test execution to avoid allocating DataSourceAttribute[] in the common “no DataSource” path by switching to an allocation-free attribute presence check.
  • Updated retry-attribute discovery to iterate the cached Attribute[] directly instead of using a yield-based iterator.
  • Expanded docs/glossary.md with new glossary entries (e.g., AwesomeAssertions, DynamicData, PropertyBag, TestNode, FV correspondence terms).
Show a summary per file
FileDescription
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.csRemoves per-test allocations by using IsAttributeDefined<DataSourceAttribute> in the DataSource execution decision.
src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.csRemoves iterator allocations by scanning cached attributes directly to find a single RetryBaseAttribute.
docs/glossary.mdAdds/updates glossary entries for testing libraries and platform concepts.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threaddocs/glossary.md Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Test Expert Reviewer 🧪
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

This PR contains no test file changes. The two production-code changes are allocation-free refactorings in the test execution hot path:

  1. TestMethodInfo.GetRetryAttribute() — replaces a yield-iterator enumeration with a direct foreach over the cached Attribute[]. Semantically equivalent, but the "throw when multiple RetryBaseAttribute are present" guard is not covered by any existing test (confirmed via grep of TestMethodInfoTests.cs and ThrowMultipleAttributesException).

  2. TestMethodRunner.TryExecuteDataSourceBasedTestsAsync() — replaces GetAttributes<DataSourceAttribute>()[Length == 1] with IsAttributeDefined<DataSourceAttribute>(). Safe because DataSourceAttribute has AllowMultiple = false by default; the subtle semantic shift from "exactly one" to "any" is therefore unreachable in practice.

Recommendations

  • Add a unit test in TestMethodInfoTests.cs verifying that applying two RetryBaseAttribute subclass instances to a method causes the expected exception. This would pin the refactored iteration logic against regression. (See inline comment.)

Generated by Test Expert Reviewer

🧪 Test quality reviewed by Test Expert Reviewer 🧪

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: PR Nitpick Reviewer 🔍
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

The two code changes are well-motivated and the logic is correct. Two minor comment/naming issues are worth a quick pass:

  1. TestMethodRunner.cs L182 – comment precision: The phrase "sealed and does not allow multiple" conflates sealed (prevents subclassing) with AllowMultiple = false (prevents multiple attribute instances). Both properties matter but for different reasons, and the current wording could mislead a reader about which one controls what.

  2. TestMethodInfo.cs L293 – variable name leaks implementation detail: cachedAttributes mirrors the helper method name's caching detail. A neutral name (methodAttributes) would be more resilient to future renames of the underlying API.

Positive Highlights

  • The guard-clause inversion in TryExecuteDataSourceBasedTestsAsync is a clear improvement in readability.
  • Both explanatory comments accurately describe why the allocation is avoided — helpful for future maintainers.
  • The glossary additions are thorough and well-written.

Recommendations

  • Clarify the comment on L182 to separate the roles of sealed and AttributeUsage.AllowMultiple = false.
  • Rename cachedAttributesmethodAttributes (or attributes) to avoid coupling the local name to the helper's implementation detail.

🔍 Meticulously inspected by PR Nitpick Reviewer

🔍 Meticulously inspected by PR Nitpick Reviewer 🔍

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Summary

Workflow: Expert Code Reviewer 🧠
Date: 2026-05-11
Repository: microsoft/testfx

Key Findings

No correctness, threading, security, or API-compatibility issues found. The two code changes are semantically equivalent to what they replace — the optimizations are sound.

TestMethodInfo.GetRetryAttribute — replaces a yield-iterator chain (GetAttributes<RetryBaseAttribute>GetEnumerator) with a direct foreach over the already-cached Attribute[]. The filtering (is RetryBaseAttribute), the dual-match detection (ThrowMultipleAttributesException, [DoesNotReturn]), and the null-return for no match are all preserved. Because ThrowMultipleAttributesException never returns, the loop exits on the second match exactly as the old enumerator code did.

TestMethodRunner.TryExecuteDataSourceBasedTestsAsync — replaces GetAttributes<DataSourceAttribute>() (yield iterator + spread-to-array) and a { Length: 1 } check with IsAttributeDefined<DataSourceAttribute>. The comment's claim "presence == exactly one" is verified: DataSourceAttribute is sealed and [AttributeUsage(Inherited = false)] with AllowMultiple = false, so it is physically impossible to have more than one instance in the cached array.

Both paths ultimately route through ReflectHelper.Instance.GetCustomAttributesCached(MethodInfo), so there is no change in which attributes are seen or how inheritance is handled.

Recommendations

None required — the changes are correct and the allocation savings are genuine hot-path wins (these methods run for every test execution).


Generated by Expert Code Reviewer 🧠 🧠

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot address review comments

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 11, 2026

Copy link
Copy Markdown
Contributor

Copilot address review comments

Done in ee2c9f5. I addressed the actionable review items (coverage test for multiple RetryBaseAttribute, DataSource comment wording, and local variable naming).

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs Outdated
CopilotAIand others added 2 commits May 11, 2026 11:38
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 11, 2026 11:39
@Evangelink
Amaury Levé (Evangelink) removed the request for review from CopilotMay 11, 2026 11:39
…d-iterator-get-retry-attribute-be0151402d39b1b6
# Conflicts:
#	docs/glossary.md
#	src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs
CopilotAI review requested due to automatic review settings May 13, 2026 10:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@Evangelink

Copy link
Copy Markdown
MemberAuthor

Copilot resolve the merge conflicts in this pull request

Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>

CopilotAI commented May 13, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in 6d3d58a by merging the latest origin/main into this branch and fixing the conflict in TestMethodRunner.cs while preserving the reviewed DataSourceAttribute cardinality behavior.

@Evangelink
Amaury Levé (Evangelink) merged commit 394d182 into mainMay 13, 2026
10 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the efficiency/avoid-yield-iterator-get-retry-attribute-be0151402d39b1b6 branch May 13, 2026 12:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink