Add MSTest reflection source generator (issue #1837)#8586
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an opt-in MSTest reflection metadata source generator (MSTestAdapter.PlatformServices.SourceGeneration) plus new runtime infrastructure in MSTestAdapter.PlatformServices to swap IReflectionOperations/IFileOperations to source-gen-backed implementations via a generated [ModuleInitializer]. This is intended as an early building block toward NativeAOT support (#1837).
Changes:
- Add a new incremental generator project that discovers
[TestClass]/[TestMethod]and emits a module initializer registering metadata. - Add runtime “source-generated” reflection/file operations and a public hook (
ReflectionMetadataHook) for generated code to register metadata. - Add unit tests for the generator and wire new projects into
TestFx.slnxandMSTest.slnf, plus updatePublicAPI.Unshipped.txt.
Show a summary per file
| File | Description |
|---|---|
| TestFx.slnx | Adds the new generator project + its unit test project to the full solution. |
| MSTest.slnf | Adds the new generator project + its unit test project to the MSTest solution filter. |
| src/Adapter/MSTestAdapter.PlatformServices/PlatformServiceProvider.cs | Adds internal hook to swap IReflectionOperations/IFileOperations. |
| src/Adapter/MSTestAdapter.PlatformServices/PublicAPI/PublicAPI.Unshipped.txt | Declares new public API surface (ReflectionMetadataHook, SourceGeneratedReflectionDataProvider). |
| src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/*.cs | Adds runtime hook + source-gen-backed reflection/file operations + toggle. |
| src/Adapter/MSTestAdapter.PlatformServices.SourceGeneration/* | Adds generator models/helpers + incremental generator + emitter + banned symbols. |
| test/UnitTests/MSTestAdapter.PlatformServices.SourceGeneration.UnitTests/* | Adds generator unit tests + test runner program + test project. |
Copilot's findings
- Files reviewed: 19/19 changed files
- Comments generated: 14
🔍 Build Failure AnalysisSummary — The build failed due to a missing interface method and enforced code style violations across multiple target frameworks. Root cause 1: Missing interface method
|
| Code | Project | File:Line | Message |
|---|---|---|---|
CS1061 |
MSTestAdapter.PlatformServices |
ReflectionMetadataHook.cs:37 |
'IPlatformServiceProvider' does not contain a definition for 'SetSourceGeneratedOperations'... (net8.0) |
CS1061 |
MSTestAdapter.PlatformServices |
ReflectionMetadataHook.cs:37 |
'IPlatformServiceProvider' does not contain a definition for 'SetSourceGeneratedOperations'... (net9.0) |
IDE0032 |
MSTestAdapter.PlatformServices |
SourceGeneratorToggle.cs:13 |
Use auto property (net8.0) |
IDE0032 |
MSTestAdapter.PlatformServices |
SourceGeneratorToggle.cs:13 |
Use auto property (net9.0) |
IDE0032 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:15 |
Use auto property (net8.0) |
IDE0032 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:15 |
Use auto property (net9.0) |
IDE0032 |
MSTestAdapter.PlatformServices |
SourceGeneratedFileOperations.cs:16 |
Use auto property (net8.0) |
IDE0032 |
MSTestAdapter.PlatformServices |
SourceGeneratedFileOperations.cs:16 |
Use auto property (net9.0) |
IDE0046 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:26 |
'if' statement can be simplified (net8.0) |
IDE0046 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:26 |
'if' statement can be simplified (net9.0) |
IDE0046 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:257 |
'if' statement can be simplified (net8.0) |
IDE0046 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:257 |
'if' statement can be simplified (net9.0) |
IDE0046 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:291 |
'if' statement can be simplified (net8.0) |
IDE0046 |
MSTestAdapter.PlatformServices |
SourceGeneratedReflectionOperations.cs:291 |
'if' statement can be simplified (net9.0) |
🤖 Generated by the Build Failure Analysis workflow · commit 6887de2
Generated by Build Failure Analysis for issue #8586 · ● 1.3M · ◷
Evangelink
left a comment
There was a problem hiding this comment.
Generated by Build Failure Analysis for issue #8586 · ● 1.3M
6887de2 to
73050ee
Compare
Consolidates the experimental MSTest.Engine package into the existing MSTest.SourceGeneration package, addresses PR review comments, and adds correctness fixes for ref/out/in parameter signatures, inaccessible nested test classes, file-local test classes, open generic test classes, overridden method deduplication, and composite-provider null handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
73050ee to
a670c54
Compare
- Delegate IReflectionOperations.GetType(string) to fallback so simple type names cannot bind to a same-named type from the wrong assembly via the composite TypesByName lookup (matches Type.GetType semantics). - Simplify HasByRefParameter to a single LINQ Any() expression. - Drop unused reassignment of GeneratorDriver in the generator test harness's RunGeneratorAndGetCompilation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes part of #1837.
What
Introduces a new Roslyn
IIncrementalGenerator(projectMSTest.SourceGeneration, undersrc/Analyzers/) that discovers[TestClass]types at compile time and emits a[ModuleInitializer]registering aSourceGeneratedReflectionDataProviderfor the user's assembly.It also adds the runtime infrastructure in
MSTestAdapter.PlatformServices(folderSourceGeneration/) to swap theIReflectionOperationsandIFileOperationsservices for source-gen-backed implementations once metadata is registered.This is the first step toward Native AOT support: when the generator runs, MSTest reads test metadata from compile-time-known data instead of doing reflection at runtime. The feature is opt-in — when no metadata is registered, the platform keeps using the existing reflection-based implementations.
Why
Issue #1837 tracks Native AOT support for MSTest. PR #8263 introduced the
IReflectionOperationsservice abstraction as a prerequisite. This PR delivers the next building block by adding the source generator that will populate that abstraction without reflection.Layout
src/Analyzers/MSTest.SourceGeneration/—ReflectionMetadataGeneratorand its emitters/models/helpers, packaged as analyzers/dotnet/cs.src/Adapter/MSTestAdapter.PlatformServices/SourceGeneration/—ReflectionMetadataHook,SourceGeneratedReflectionDataProvider,CompositeSourceGeneratedReflectionDataProvider,SourceGeneratedReflectionOperations,SourceGeneratedFileOperations,SourceGeneratorToggle.PlatformServiceProvider: a singleinternalSetSourceGeneratedOperationsmethod used byReflectionMetadataHookto swap providers.src/Adapter/MSTest.Engine/project and its unit tests, which the new design replaces. (This is what shows up in the solution-file diff.)test/UnitTests/MSTest.SourceGeneration.UnitTests/.PublicAPI.Unshipped.txtupdated.Scope (MVP)
[ModuleInitializer]that callsReflectionMetadataHook.SetMetadata.[TestClass]+[TestMethod]), static/abstract classes being skipped, empty-assembly emission, and a Roslyn-compiles-cleanly smoke test.Known follow-ups (out of scope for this PR)
These are intentionally deferred to keep this PR reviewable:
AssemblyAttributes,TypeAttributes,TypeProperties,TypeMethodAttributes,TypeConstructorsInvoker,TypeMethodLocations). The MVP only emits the assembly name, type list, and per-type method list.[ModuleInitializer]polyfill fornetstandard2.0/ older TFM consumers.EnableMSTestSourceGeneration).DataRow/DynamicData/ inherited attributes / base-class roll-up.AssemblyInitialize/ClassInitialize/TestInitializelifecycle.Validation
Built locally with
build.cmd -c Debug(0 warnings, 0 errors).Notes for reviewers
ReflectionMetadataHook.SetMetadataand theSourceGeneratedReflectionDataProvidershape are public, because the generated module initializer needs to call them.SourceGeneratorToggleusesInterlocked.Exchangeto ensure exactly-once swap and to make the toggle race-free if multiple assemblies' module initializers run.ReflectionMetadataGeneratorskipsstaticandabstracttypes — they cannot be discovered as test classes.IReflectionOperations.GetType(string)delegates straight to the fallback so callers cannot bind to a same-named type from the wrong assembly via the compositeTypesByNamelookup; the assembly-qualifiedGetType(Assembly, string)overload is the one that uses the source-generated data.