Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101
, '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

Use generated descriptors for MTP discovery - #10777

Merged
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path
Aug 27, 2026
Merged

Use generated descriptors for MTP discovery#10777
Amaury Levé (Evangelink) merged 9 commits into
mainfrom
dev/amauryleve/implement-generated-descriptor-path

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Add a bounded MTP-only generated descriptor discovery path for plain synchronous [TestMethod] and [DataRow] methods.

Generated methods that declare complete support bypass the legacy runtime method enumeration and validation pass. Mixed classes fall back per method, while VSTest continues using the existing discovery path.

Old and new flow

Before:
generated registry -> MethodInfo registration -> runtime method scan/validation
-> UnitTestElement -> existing lifecycle/execution -> MTP TestNode
After (supported subset):
generated descriptor -> UnitTestElement
-> existing lifecycle/execution -> MTP TestNode
Fallback:
unsupported generated method -> existing runtime method scan/validation

This deliberately retains UnitTestElement, filtering, lifecycle, execution, and result conversion. It is the first production vertical slice, not a second lifecycle engine.

Supported subset

The fast path is limited to complete generated descriptors for public, instance, non-abstract, non-async, void methods with the exact built-in TestMethodAttribute and optional DataRowAttributes.

The legacy path remains authoritative for:

  • async/Task/ValueTask methods;
  • custom TestMethodAttribute implementations;
  • DynamicData/custom ITestDataSource;
  • incomplete or unsupported metadata;
  • ambiguous overloads and mixed classes;
  • VSTest.

The selection is observable internally through generated-descriptor metadata and focused path-selection tests.

Impact

Projects/layers changed:

  • MSTest.SourceGeneration models and emitters expose descriptor capability.
  • PlatformServices registration/provider surfaces descriptor methods.
  • MTP discovery consumes supported descriptors and falls back per method.
  • Existing lifecycle, TestContext, execution, retry, timeout, cleanup, filtering, and result pipelines are unchanged.

Controlled discovery benchmark, 10,000 iterations with two tests per iteration and five samples:

  • allocations: 114,451,416 B -> 89,573,624 B (-21.7%);
  • median elapsed: 508.162 ms -> 415.724 ms (-18.2%), but one sample regressed, so no strong wall-clock claim is made.

Release assembly size cost:

  • MSTest.SourceGeneration: +8,192 B (+4.94%);
  • MSTestAdapter.PlatformServices net8.0: +11,264 B (+2.13%).

Validation

  • MSTest.SourceGeneration.UnitTests: 127/127 passed.
  • MSTestAdapter.PlatformServices.UnitTests net8.0: 1,081/1,081 passed.
  • Managed ReflectionFree acceptance: 2/2 passed (net8.0 and net10.0).
  • NativeAOT acceptance: 2/2 passed.
  • Release pack: succeeded with 0 warnings and 0 errors.
  • Three independent reviews covered correctness, architecture/compatibility, and performance.

Future phases

This PR does not bypass UnitTestElement, TypeCache during execution, the lifecycle engine, or MTP result conversion. Follow-up work can introduce a generated execution abstraction and broader descriptor eligibility while preserving this per-method fallback boundary.

Bypass legacy method enumeration and validation for the bounded generated synchronous TestMethod and DataRow subset while retaining per-method fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd0b7d0f-8590-4c8b-ae58-635c652c60ef
CopilotAI balanced review requested due to automatic review settings August 26, 2026 15:54

CopilotAI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Build the repository

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

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

Adds an MTP-only fast discovery path using generated descriptors while preserving legacy fallback and VSTest behavior.

Changes:

  • Extends source-generation metadata with descriptor eligibility and completeness.
  • Uses descriptors during MTP discovery with per-method fallback.
  • Adds unit, acceptance, and NativeAOT coverage.
Show a summary per file
FileDescription
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.csSupports generated reflection providers in tests.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/MockableReflectionOperations.csForwards descriptor lookups.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/SourceGeneration/SourceGeneratedReflectionOperationsTests.csTests descriptor retrieval and completeness.
test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.csTests fast-path selection and fallback.
test/UnitTests/MSTest.SourceGeneration.UnitTests/MSTestReflectionMetadataGeneratorTests.csVerifies generated descriptor metadata.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SourceGenerationNonAotTests.csChecks non-AOT generated output.
test/IntegrationTests/MSTest.Acceptance.IntegrationTests/NativeAotTests.csChecks NativeAOT eligibility metadata.
src/Analyzers/MSTest.SourceGeneration/Models/TestClassModel.csAdds descriptor capability fields.
src/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.csDetermines descriptor eligibility.
src/Analyzers/MSTest.SourceGeneration/Generators/RuntimeRegistrationEmitter.csEmits descriptor registration.
src/Analyzers/MSTest.SourceGeneration/Generators/MetadataRegistryEmitter.csEmits descriptor metadata properties.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionOperations.csExposes registered descriptors.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/SourceGeneratedReflectionDataProvider.csStores descriptor data.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/ReflectionMetadataHook.csRegisters descriptor metadata.
src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/CompositeSourceGeneratedReflectionDataProvider.csMerges descriptor providers.
src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.csProvides reflection-mode fallback.
src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txtTracks the new public overload.
src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.csMarks descriptor-originated tests.
src/Adapter/MSTestAdapter.PlatformServices/Interfaces/IReflectionOperations.csDefines descriptor lookup.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.csConsumes descriptors during discovery.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.csEnables descriptors for MTP.
src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.csPropagates descriptor selection.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 22/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

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

Review Summary — PR #10777

This PR introduces source-generated test descriptors for native MTP discovery, allowing the discovery path to skip the legacy runtime-method scan when generated metadata is complete. The design is well-layered: the source generator emits per-method/per-class support flags, the ReflectionMetadataHook.Register overload carries the new dictionaries, and the TypeEnumerator consumes them with a clean fallback path.

Verdict Table

#DimensionVerdict
1Algorithmic Correctness⚠️ Merge semantics for DescriptorCompleteTypes are last-writer-wins — may silently produce wrong result when multiple providers register the same type
2Threading & Concurrency✅ N/A — no new shared mutable state introduced
3Security & IPC✅ N/A
4Public API & Binary Compat✅ New Register overload added (additive), old overload delegates. PublicAPI.Unshipped.txt updated.
5Performance & Allocations✅ Good — List pre-sized, HashSet used for skip-set
6Cross-TFM Compatibility✅ N/A — no TFM-specific APIs used
7Resource & IDisposable✅ N/A
8Defensive Coding✅ Null checks on new parameters, graceful fallback when descriptors unavailable
9Localization✅ N/A
10Test Isolation✅ Tests set up their own providers
11Assertion Quality✅ Uses AwesomeAssertions per project policy
12Flakiness Patterns✅ N/A
13Test Completeness✅ Good coverage of complete, incomplete, and non-MTP paths
14Data-Driven Test Coverage✅ N/A
15Code Structure⚠️ Minor — redundant ternary in AssemblyEnumerator
16–22Remaining dimensions✅ N/A or clean

Key Findings

  1. MAJOR — CompositeSourceGeneratedReflectionDataProvider merge semantics: MergeInto for DescriptorCompleteTypes uses last-writer-wins. When two providers disagree on completeness for the same type, correctness depends on registration order. The safe semantic is logical AND. Same concern applies to DescriptorTestMethods (arrays should be concatenated, not overwritten).

  2. Minor — Redundant ternary: The call in AssemblyEnumerator.DiscoverTestsInType could use the two-arg overload directly.

  3. Minor — Name-based duplicate detection vs. signature-based: TestClassModelBuilder disqualifies overloaded methods by name, while the runtime uses ToString() (includes signature). This is conservative but worth documenting.

Overall this is a solid, well-tested addition. The merge-semantics issue (finding #1) is the only one that could cause a real bug in multi-assembly/multi-provider scenarios.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 144.4 AIC · ⌖ 1.22 AIC · ⊞ 16.9K ·

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Build Failure Analysis

Summary — The Linux Release build failed because dotnet format/analyzer style errors (IDE0306/IDE0028, promoted to build errors) fire on a new line added by this PR.

Root cause: Collection initialization can be simplified

TypeEnumerator.GetTests was modified to build descriptorMethodSet via new HashSet<MethodInfo>(descriptorMethods). The repo's style analyzers require the collection-expression form ([.. descriptorMethods]) instead of the constructor-with-argument form, so IDE0306/IDE0028 are raised and — since this repo builds with analyzers as errors — the build fails.

Affected files / errors

Proposed fix

- : new HashSet<MethodInfo>(descriptorMethods);+ : [.. descriptorMethods];

Build overview
  • MSBuild: 18.11.0-1.26420.103+1d599674e
  • Projects: 51, Errors: 7, Warnings: 1
  • Failed projects: Build.proj, NonWindowsTests.slnf, MSTestAdapter.PlatformServices.csproj, MSTest.TestAdapter.csproj
All MSBuild errors (7)
CodeProjectFile:LineMessage
IDE0306MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
IDE0028MSTestAdapter.PlatformServicesTypeEnumerator.cs:99Collection initialization can be simplified
(duplicated across multiple targets, same root cause)

🤖 Generated by the Build Failure Analysis workflow using (a href="(dev.azure.com/redacted) · commit 025fb6e

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Build Failure Analysis workflow. · auto · 82.8 AIC · ⌖ 1.54 AIC · ⊞ 13.3K ·

Comment threadsrc/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs Outdated
Preserve legacy discovery for partial and unresolved generated methods, merge repeated descriptor registrations conservatively, and record the internal API additions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:16
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 272.7 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Aug 27, 2026
Verify inaccessible generated test methods keep the containing class on legacy discovery without expecting metadata that is intentionally omitted.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:38
@github-actions

This comment has been minimized.

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.

Review details

  • Files reviewed: 23/23 changed files
  • Comments generated: 2
  • Review effort level: Balanced

@github-actions

This comment has been minimized.

Exercise generated descriptor selection through the MTP discovery wrapper while preserving VSTest fallback, and pin unsupported execution-shape guards.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 14:54

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.

Review details

  • Files reviewed: 24/24 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment threadsrc/Analyzers/MSTest.SourceGeneration/Generators/TestClassModelBuilder.cs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 191.6 AIC · ⌖ 1.19 AIC · ⊞ 16.9K ·

Comments that could not be inline-anchored

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:262

🧪 Test review · Grade D (60–69) — Registers a method into the process-global ReflectionMetadataHook.Composite registry with no reset, so the registration persists across the whole test-assembly run.

Reset the registration after the test (or route registration through a disposable/mockable seam) so other tests can't observe GeneratedDescriptorTestClass as permanently registered.

Replacement: none

Classify overrides from the same inherited attribute set used for emitted metadata so custom inherited TestMethod attributes retain legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:09

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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Detect test attributes on non-ordinary methods and property or event accessors so generated discovery never suppresses tests only visible to the legacy runtime scan.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 27, 2026 15:25
@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 238.2 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Verify complete descriptors exclude unregistered methods and partial test classes include methods from every declaration while retaining legacy fallback.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 212.5 AIC · ⌖ 1.25 AIC · ⊞ 16.9K ·

Assert descriptor support per partial-class method and mark the process-wide registration test non-parallel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Expert test review — PR #10777

GradeTestMutationNotesHow to improve
B (80–89)new TypeEnumeratorTests.
EnumerateShouldSelectPlainAndDataRowDescriptorsWhenComplete
2/3 killedRelies implicitly on isValidTestMethod:false to prove no fallback ran; unlike sibling tests it never asserts _mockTestMethodValidator was not invoked.Add _mockTestMethodValidator.Verify(..., Times.Never) as the sibling tests do.
A (90–100)new AssemblyEnumeratorWrapperTests.
GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp
4/4 killedRegisters real metadata via ReflectionMetadataHook and asserts IsFromGeneratedDescriptor differs between MTP and VSTest paths, with proper Instance restore in finally.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldUseCompleteGeneratedDescriptorsWithoutLegacyMethodValidation
3/3 killedConfirms descriptor-only path both selects the right method and explicitly verifies validator is never consulted.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldFallBackPerMethodWhenGeneratedDescriptorsAreIncomplete
4/4 killedVerifies mixed descriptor/fallback membership, per-test IsFromGeneratedDescriptor flag, and that the validator is skipped only for the descriptor method.
A (90–100)new TypeEnumeratorTests.
EnumerateShouldIgnoreGeneratedDescriptorsOutsideNativeMtp
2/2 killedConfirms the legacy Enumerate(warnings) overload never marks results as generated-descriptor sourced.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
ReturnsRegisteredMethodsAndCompleteness
3/3 killedDirectly asserts the returned method identity and completeness flag from a single provider registration.
A (90–100)new SourceGeneratedReflectionOperationsTests.
TryGetTestMethodDescriptors_
RetainsPerMethodFallbackWhenRegistrationIsIncomplete
4/4 killedExercises composite-provider merge semantics, proving completeness is AND-ed across conflicting providers.
A (90–100)mod NativeAotTests.
NativeAotTests_WillRunWithExitCodeZero
2/2 killedNew assertions pin IsDescriptorSupported true/false for synchronous vs async generated methods in the published registry.
A (90–100)mod SourceGenerationNonAotTests.
SourceGenerationNonAot_BuildsAndRunsTests_WithExitCodeZero
3/3 killedConfirms new descriptor-support and registration-array flags are emitted end-to-end for the non-AOT reflection-free path.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
DeclaresDescriptorSupportOnlyForBoundedSynchronousSubset
4/4 killedUses positional substring checks to pin per-method IsDescriptorSupported for DataRow, attribute-fallback, and async cases.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InaccessibleTestMethodRetainsLegacyDiscoveryFallback
3/3 killedConfirms a private [TestMethod] is excluded from the registry entirely while the public sibling still gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
UnsupportedExecutionShapesRetainLegacyDiscoveryFallback
4/4 killedLoops over static, Task-returning, ValueTask-returning, and async-void shapes and asserts each is marked unsupported.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
PartialTestClassRetainsLegacyDiscoveryFallback
3/3 killedVerifies descriptor support is granted per-method even when the [TestClass] declaration is split across partial declarations.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
InheritedCustomTestMethodOverrideRetainsLegacyDiscoveryFallback
3/3 killedConfirms an inherited custom TestMethodAttribute override falls back to legacy discovery while a plain sibling gets descriptor support.
A (90–100)new MSTestReflectionMetadataGeneratorTests.
Generator_
TestMethodAccessorRetainsLegacyDiscoveryFallback
3/3 killedConfirms a [TestMethod] on a property accessor is excluded from the registry while a normal method sibling is supported.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Suggestions on the Files
changed tab can be applied with one click. Re-run with
/review-tests.

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K · [◷]( · )

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Automated content by GitHub Copilot. Generated by the Test Reviewer on PR (on open / sync) workflow. · auto · 153.5 AIC · ⌖ 1.23 AIC · ⊞ 16.9K ·

@github-actions

This comment has been minimized.

Pin that complete generated descriptor sets never invoke legacy method validation.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🧵 Parallel-safety audit — PR #10777

Parallelization — one row per test assembly audited:

Test assemblyScopeWorkersAnalyzer coverage
MSTestAdapter.PlatformServices.UnitTestsoff (uses TestFramework.ForTestingMSTest.TestContainer, which has no parallel scheduler)n/an/a
MSTest.SourceGeneration.UnitTestsoff (same internal test engine)n/an/a
MSTest.Acceptance.IntegrationTests (NativeAotTests, SourceGenerationNonAotTests)not determined from this diff (no [assembly: Parallelize]/[assembly: DoNotParallelize] change here)n/an/a

⚠️ The two unit-test projects touched by this PR run on the internal TestContainer-based engine, which has no parallel scheduler at all. Everything below is a readiness checklist — what to fix before any such code is exercised under real MSTest parallelization — not a live race today. Severities capped at Warning.

Findings: A (global-state) 1 · B (paths) 0 · C (declaration) 1 · D (over-serialization) 0 — by severity: Warning 1 · Info 1.

Top actions (by expected value):

  1. If GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp (or similar tests mutating PlatformServiceProvider.Instance) is ever ported to a real MSTest-parallel suite, keep the existing save/restore-in-finally pattern and pair it with an explicit [ResourceLock]/[DoNotParallelize] declaration rather than relying on [DoNotParallelize] alone if the goal is future MethodLevel-safe coordination.
  2. No shared-filesystem-path or over-serialization issues found in the changed lines.

Warning (readiness)

  • [A · High confidence]test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorWrapperTests.cs:99-127 — new test GetTestsShouldSelectGeneratedDescriptorsOnlyForMtp sets the process-global singleton PlatformServiceProvider.Instance = null then re-registers a custom provider, mutating the same static field the constructor (:28) and Dispose (:37) of this class already set/reset per test. This is exactly the kind of process-global mutation category A flags. It is correctly declared [DoNotParallelize] (line 89) and the value is restored in a finally block (line 127), so it is well-guarded for this engine. Under the current TestContainer engine there is no scheduler so this cannot race today; it is readiness-only. Fix (if/when ported to real MSTest parallel execution): keep [DoNotParallelize] (or a dedicated [ResourceLock] key) and the try/finally restore — this is already the correct pattern, just note it explicitly rather than relying on convention, since other tests in the same file (constructor/Dispose) touch the same static without any lock declaration.

Info

  • [C · Low confidence] Constructor/Dispose of AssemblyEnumeratorWrapperTests (pre-existing, not changed by this PR) set/reset PlatformServiceProvider.Instance on every test without any [ResourceLock]/[DoNotParallelize] declaration. This is pre-existing (outside the PR's changed ranges) and reported here only as context — not attributable to this PR — since this file already establishes the pattern the new test follows.

Nothing else in the changed lines (TypeEnumeratorTests.cs, SourceGeneratedReflectionOperationsTests.cs, MockableReflectionOperations.cs, TestablePlatformServiceProvider.cs, the acceptance-test assertions in NativeAotTests.cs / SourceGenerationNonAotTests.cs, and the raw [assembly: Parallelize(...)] strings embedded as source-generator test input in MSTestReflectionMetadataGeneratorTests.cs, which configure a synthetic compiled sample, not this test assembly) mutates process-global state, shared filesystem paths, or declares/removes [ResourceLock] / [DoNotParallelize] / [Parallelize] on a real assembly.

Advisory only — heuristic, non-blocking. Re-run with /parallel-audit. This audit answers "is it parallel-safe?"; for testability, smells, or flakiness see the detect-static-dependencies / test-smell-detection / test-anti-patterns analyses.

🤖 Automated content by GitHub Copilot. Generated by the Parallel-safety audit on PR (on open / sync) workflow. · auto · 112.7 AIC · ⌖ 2.11 AIC · ⊞ 24.8K · [◷]( · )

@Evangelink
Amaury Levé (Evangelink) merged commit 4de347e into mainAug 27, 2026
38 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/implement-generated-descriptor-path branch August 27, 2026 18:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-reviewAwaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Evangelink@0101