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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 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

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

Merged
Amaury Levé (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
Amaury Levé (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-runcontextJuly 5, 2026 19:24
@Evangelink
Amaury Levé (Evangelink) marked this pull request as ready for review July 5, 2026 19:27
…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
Amaury Levé (Evangelink)force-pushed the dev/amauryleve/vstest-decoupling-conversion branch from bb13e25 to 8832952CompareJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) changed the base branch from dev/amauryleve/vstest-decoupling-runcontext to mainJuly 5, 2026 19:44
@Evangelink
Amaury Levé (Evangelink) enabled auto-merge (squash) July 5, 2026 19:52
@EvangelinkAmaury Levé (Evangelink) added the state/needs-review Awaiting review from the team. label Jul 5, 2026
@Evangelink
Amaury Levé (Evangelink) merged commit 5bd3c24 into mainJul 5, 2026
17 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the dev/amauryleve/vstest-decoupling-conversion branch July 5, 2026 20:42
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.

2 participants

@Evangelink@0101