Skip to content

Relocate the UnitTestElement<->TestCase conversion to the adapter (Phase 6e-3b)#9626

Merged
Evangelink merged 13 commits into
mainfrom
dev/amauryleve/vstest-decoupling-conversion
Jul 5, 2026
Merged

Relocate the UnitTestElement<->TestCase conversion to the adapter (Phase 6e-3b)#9626
Evangelink merged 13 commits into
mainfrom
dev/amauryleve/vstest-decoupling-conversion

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Phase 6e-3b — Relocate the UnitTestElementTestCase conversion to the adapter

Part of the initiative to make MSTestAdapter.PlatformServices platform-agnostic by removing its dependency on the VSTest object model (Microsoft.TestPlatform.ObjectModel). VSTest coupling moves up into MSTest.TestAdapter; the platform-services engine becomes neutral. Pure relocation — no behavior change.

What moves (all invoked only at the adapter boundary today)

  • UnitTestElement.ToTestCase() / GetOrCreateHostTestCase() + the test-case Id hashing (GenerateSerializedDataStrategyTestId / VersionedGuidFromHash) → new UnitTestElementExtensions in MSTest.TestAdapter/Extensions. ToTestCase/GetOrCreateHostTestCase were already called only from the adapter (executor, discoverer, recorder, filter, sink).
  • The EngineConstants #region Test Property registration — every TestProperty id/label/valueType/attribute, plus the TCM/TFS label constants — → new AdapterTestProperties class in the adapter. EngineConstants keeps only its neutral members (extensions, fixture traits, executor URI) and no longer references the VSTest object model.
  • TestCaseExtensions and TcmTestPropertiesProvider (already adapter-namespaced) → physically moved into MSTest.TestAdapter.

Coupling impact

UnitTestElement, EngineConstants, TestCaseExtensions, and TcmTestPropertiesProvider all stop referencing Microsoft.VisualStudio.TestPlatform.ObjectModel, dropping the PlatformServices coupling from 13 → 10 files. The remaining 10 are the settings/utility residuals (6e-4).

Fidelity — the ID invariant (byte-identical proof)

Per the review guardrail, the correctness-critical bodies moved byte-for-byte:

  • AdapterTestProperties TestProperty region: Compare-Object of the old EngineConstants region vs the new class body reports IDENTICAL (every TestProperty.Register id/label/valueType/attribute unchanged).
  • VersionedGuidFromHash: Compare-Object of old vs new body reports IDENTICAL.
  • ToTestCase / GenerateSerializedDataStrategyTestId differ only by the mechanical this.element. / local testMethod and EngineConstants.XAdapterTestProperties.X prefix rewrites; the algorithm, order, and hash inputs are unchanged.

Empirically: UnitTestElementTests' exact test-Id GUID assertions pass, and the cross-process discovery→execution integration suite is green — proving the serialized-test-case caches and id correlation are preserved.

Deliberately deferred to stay byte-for-byte (both remain open): the single ToTestCase in the test-case filter (#9568) and CloneWithUpdatedSource's mutate-this behavior (#9573). Naming: AdapterTestProperties avoids clashing with the VSTest TestCaseProperties type.

Verification

  • Full build.cmd -c Debug green across all TFMs (net462/net8.0/net9.0/UWP/WinUI), IDE0005 clean.
  • MSTestAdapter.PlatformServices.UnitTests: 897 (net8.0) / 935 (net462), 0 failed.
  • MSTestAdapter.UnitTests: 21, 0 failed.
  • MSTest.IntegrationTests (cross-proc, net462): 48 total, 0 failed, 1 skipped (OutputIsNotMixedWhenTestsRunInParallel, pre-existing known flaky).

Stacking

Stacked chain onto dev/amauryleve/vstest-decoupling-base:
6c2 #95906d-1 #95916d-2 #96216e-1 #96226e-2 #96236e-3a #96246e-3b (this).
Base for this PR is the 6e-3a branch (dev/amauryleve/vstest-decoupling-traits). Review/merge after the predecessors reach the base. Do not rebase/reset the base.

Base automatically changed from dev/amauryleve/vstest-decoupling-traits to dev/amauryleve/vstest-decoupling-runcontext July 5, 2026 19:24
@Evangelink Evangelink marked this pull request as ready for review July 5, 2026 19:27
Evangelink and others added 13 commits July 5, 2026 21:36
…orm-agnostic effort) (#9572)

Co-authored-by: Amaury Leveque <amauryleve@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-agnostic effort) (#9576)

Co-authored-by: Amaury Leveque <amauryleve@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Leveque <amauryleve@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…(Phase 6c) (#9585)

Co-authored-by: Amaury Leveque <amauryleve@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the VSTest IRunSettings with a neutral settings-XML string through the
isolation-host layer, removing IRunSettings from MSTestAdapter.PlatformServices.

- IPlatformServiceProvider.CreateTestSourceHost, TestSourceHost (both ctors) and
  AssemblyEnumeratorWrapper.GetTests/GetTestsInIsolation now take string? settingsXml.
- TestExecutionManager.CacheSessionParameters takes the settings-XML string directly.
- Callers extract runContext?.RunSettings?.SettingsXml / discoveryContext?.RunSettings?.SettingsXml
  at the point they already had the (still VSTest) run/discovery context; only .SettingsXml
  (a string) was ever read off IRunSettings, so this is byte-for-byte.

The remaining IRunContext/IDiscoveryContext usage is the test-case filter (deferred to the
filter sub-phase). No behavior change: the appdomain DisableAppDomain decision and the
run-parameter caching read the same settings XML as before.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move TestMethodFilter (and its nested TestElementFilter) out of MSTestAdapter.PlatformServices
up into MSTest.TestAdapter, and inject the neutral ITestElementFilter into the engine and
discoverer via a new ITestElementFilterProvider abstraction.

- New neutral ITestElementFilterProvider (PlatformServices.Interface): the boundary builds it
  (TestElementFilterProvider, closing over the VSTest IRunContext/IDiscoveryContext) and passes it
  into TestExecutionManager.RunTestsAsync/ExecuteTestsAsync and UnitTestDiscoverer.DiscoverTests.
- The engine/discoverer invoke the provider at the EXACT points they previously built the filter
  (per source), so filter parse-error reporting keeps the same timing and per-source semantics;
  TestElementFilter.Matches still does element.ToTestCase() (byte-for-byte; #9568 deferred).
- This removes ITestCaseFilterExpression / GetTestCaseFilter / MatchTestCase / the VSTest
  TestProperty filter set from PlatformServices code. IRunContext/IDiscoveryContext remain only for
  deployment + settings extraction (removed in a follow-up).

No behavior change: filtered set/order and the discovery/execution filterHasError bail-out are
identical; TestCaseFilteringTests (out-of-proc filter regression net) stays green.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the VSTest IRunContext/IDiscoveryContext with neutral primitives extracted at the
adapter boundary, so MSTestAdapter.PlatformServices no longer references either type.

- Execution: MSTestExecutor builds the neutral DeploymentContext (test-run directory + run
  settings XML) from the host run context and injects it into TestExecutionManager.RunTestsAsync/
  ExecuteTestsAsync/ExecuteTestsInSourceAsync/Deploy (DeploymentContext un-guarded so it is the
  single execution-inputs carrier on all TFMs).
- Discovery: MSTestDiscoverer passes the run settings XML string into UnitTestDiscoverer.
  DiscoverTests/DiscoverTestsInSource; MSTestDiscovererHelpers.InitializeDiscovery and
  MSTestSettings.PopulateSettings take string? settingsXml.
- Only .SettingsXml + .TestRunDirectory were ever read off the contexts, so this is byte-for-byte.

IRunContext/IDiscoveryContext are now absent from PlatformServices code (doc comments only); the
remaining ObjectModel.Adapter surface is the IFrameworkHandle-backed deploy/recorder/logger handles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…hase 6e-1)

The execution engine used the VSTest IFrameworkHandle exclusively to obtain an
IAdapterMessageLogger via ToAdapterMessageLogger(). Replace the IFrameworkHandle parameter
with the neutral IAdapterMessageLogger throughout TestExecutionManager (RunTestsAsync both
overloads, ExecuteTestsAsync, ExecuteTestsInSourceAsync, Deploy); the adapter boundary
(MSTestExecutor) now calls frameworkHandle.ToAdapterMessageLogger() once and injects the result.

This removes the last VSTest ObjectModel.Adapter reference from the execution engine. No behavior
change: the logger wrapper is stateless, so injecting one instance is identical to building one per
call site.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move the three remaining VSTest-object-model bridge helpers out of
MSTestAdapter.PlatformServices and into MSTest.TestAdapter:
AdapterMessageLoggerExtensions, MessageLevel (ToTestMessageLevel), and
UnitTestElementSinkExtensions. These are the last code references to
Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging /
ITestCaseDiscoverySink in PlatformServices; only doc comments now mention
the VSTest types. The logical namespace is unchanged so callers at the
adapter boundary and the integration harness are unaffected.

PlatformServices.UnitTests calls the ToAdapterMessageLogger bridge, which
now lives in MSTest.TestAdapter; touching that module runs its
[ModuleInitializer] (MSTestExecutor.SetPlatformLogger), which assigns
PlatformServiceProvider.Instance.AdapterTraceLogger. Make the test double's
setter tolerate the assignment (as the real PlatformServiceProvider does)
instead of throwing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the VSTest object-model Trait type carried on UnitTestElement.Traits
with a neutral, platform-agnostic TestTrait { Name, Value } struct. The
engine-side producers and consumers (ReflectHelper/ReflectionHelper
GetTestPropertiesAsTraits, TypeEnumerator, TestExecutionManager TestContext
building, TestRunInfo, the test-filter context) now operate on TestTrait, so
five files stop referencing Microsoft.VisualStudio.TestPlatform.ObjectModel.
The VSTest Trait only survives at the adapter conversion boundary
(TestCaseExtensions and UnitTestElement.ToTestCase), which convert between
TestTrait and the host trait type.

TestTrait is [Serializable] on .NET Framework because UnitTestElement is
serialized across app domains during isolated discovery/execution; order and
Name/Value are preserved, so trait -> TestContext reporting and the produced
host test case are byte-for-byte identical.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ase 6e-3b)

Move the deep VSTest-object-model conversion out of MSTestAdapter.PlatformServices
and into MSTest.TestAdapter, as a pure relocation (no behavior change):

- UnitTestElement.ToTestCase()/GetOrCreateHostTestCase() and the test-case Id
  hashing (GenerateSerializedDataStrategyTestId / VersionedGuidFromHash) become
  UnitTestElementExtensions in the adapter. The Id hashing moves byte-identical
  (VersionedGuidFromHash verbatim), preserving cross-version discovery->execution
  test-id correlation.
- The EngineConstants '#region Test Property registration' (every TestProperty
  id/label/valueType/attribute, plus the TCM/TFS label constants) moves verbatim
  into a new adapter AdapterTestProperties class. EngineConstants keeps only its
  neutral members (extensions, fixture traits, executor uri) and no longer
  references the VSTest object model.
- TestCaseExtensions and TcmTestPropertiesProvider (already adapter-namespaced)
  move physically into MSTest.TestAdapter.

UnitTestElement, EngineConstants, TestCaseExtensions and TcmTestPropertiesProvider
all stop referencing Microsoft.VisualStudio.TestPlatform.ObjectModel, dropping the
PlatformServices coupling from 13 to 10 files. The conversion is still invoked only
at the adapter boundary (executor/discoverer/recorder/filter). The single ToTestCase
in the test-case filter and CloneWithUpdatedSource are left as-is to keep this change
byte-for-byte (#9568 and #9573 remain open).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Evangelink Evangelink force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952 Compare July 5, 2026 19:44
@Evangelink Evangelink changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to main July 5, 2026 19:44
@Evangelink Evangelink enabled auto-merge (squash) July 5, 2026 19:52
@Evangelink Evangelink added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink Evangelink merged commit 5bd3c24 into main Jul 5, 2026
17 checks passed
@Evangelink Evangelink deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state/needs-review Awaiting review from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants