Resolve the object-model assembly by name in AppDomain wiring (Phase 6e-4b)#9628
Conversation
…6e-4b) Remove the compile-time VSTest object-model dependency from the netfx AppDomain wiring: AppDomainUtilities used typeof(TestCase).Assembly to (1) add the object-model assembly's directory to the child app-domain's resolution paths and (2) anchor the 11.0 -> current binding redirect. Both run parent-side during test source host setup, after the adapter has already loaded the object model, so the assembly is resolved by simple name from the current domain instead - returning the same (post-redirect) assembly identity the type reference did, without a compile-time reference. The only remaining mention of the object model in this file is the assembly's simple name as a string literal (used for the lookup and, formerly, by the resolver's skip-list), which is not an assembly reference and does not block dropping the package. Proven on the netfx AppDomain scenario the type reference protects: PlatformServices.Desktop.IntegrationTests (assembly-resolution-from-runsettings + deployment app-domain paths) stays green (15/15). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
8e5f3bf
into
dev/amauryleve/vstest-decoupling-settings-utils
There was a problem hiding this comment.
Note
🤖 Automated review by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Expert Code Review workflow. To request a follow-up action, reply by tagging @copilot directly.
| # | Dimension | Verdict |
|---|---|---|
| 5/15 | Performance / Code Structure | 🟢 1 NIT |
✅ 21/22 dimensions clean.
- Performance —
GetObjectModelAssembly()called twice inSetConfigurationFile; could cache to a localAssemblyNamebefore theifcheck (see inline comment)
Context: This is a retrospective review of a merged PR. The change itself is a well-executed, strictly-scoped refactor. The assembly-by-name resolution is correct for the caller's lifecycle guarantee (object model always loaded before these methods run), the fallback Assembly.Load is protected by both callers' existing try/catch blocks, and the #if NETFRAMEWORK guard keeps this entirely off modern TFMs. The only actionable note is the cosmetic double-call, which is a carry-forward from the original code structure.
| string currentVersionOfObjectModel = GetObjectModelAssembly().GetName().Version.ToString(); | ||
| if (!string.Equals(currentVersionOfObjectModel, ObjectModelVersionBuiltAgainst, StringComparison.Ordinal)) | ||
| { | ||
| AssemblyName assemblyName = typeof(TestCase).Assembly.GetName(); | ||
| AssemblyName assemblyName = GetObjectModelAssembly().GetName(); |
There was a problem hiding this comment.
[NIT] Performance / Code Structure
GetObjectModelAssembly() is called twice within the same try-block. Unlike the old typeof(TestCase).Assembly (a JIT-inlined runtime constant), each call to GetObjectModelAssembly() allocates a LINQ query over AppDomain.CurrentDomain.GetAssemblies(). The practical cost is negligible in a setup path, but the logic can be simplified to a single call:
// Capture once — avoids two linear scans of GetAssemblies()
AssemblyName assemblyName = GetObjectModelAssembly().GetName();
string currentVersionOfObjectModel = assemblyName.Version!.ToString();
if (!string.Equals(currentVersionOfObjectModel, ObjectModelVersionBuiltAgainst, StringComparison.Ordinal))
{
byte[] configurationBytes =
XmlUtilities.AddAssemblyRedirection(
testSourceConfigFile,
assemblyName,
ObjectModelVersionBuiltAgainst,
assemblyName.Version!.ToString());
appDomainSetup.SetConfigurationBytes(configurationBytes);
}This also surfaces the Version! null-annotation more explicitly — AssemblyName.Version is nullable, though it will never be null for the VSTest ObjectModel in practice.
Phase 6e-4b — Resolve the object-model assembly by name in the netfx AppDomain wiring
Part of the initiative to make
MSTestAdapter.PlatformServicesplatform-agnostic by removing its dependency on the VSTest object model (Microsoft.TestPlatform.ObjectModel). Strict byte-for-byte refactor.What this does
AppDomainUtilities(netfx) usedtypeof(TestCase).Assemblyto:Both run parent-side during test-source-host setup (
GetTargetFrameworkVersionString/SetConfigurationFile), after the adapter has already loaded the object model into the current domain. So the assembly is now resolved by simple name from the current domain:This returns the same (post-redirect) assembly identity the type reference did, without a compile-time reference — so the resolution path and the generated binding redirect are unchanged. The only remaining mention of the object model in this file is its simple name as a string literal (not an assembly reference — does not block dropping the package).
This removes the real code dependency from
AppDomainUtilities. (XmlUtilities.AddAssemblyRedirectionhere is the adapter's own neutral type, not VSTest's.)Fidelity proof
Per the review guardrail for this — the last real netfx-isolation risk in the initiative — the swap is proven on the exact scenario the type reference protects:
PlatformServices.Desktop.IntegrationTests(net462 AppDomain assembly-resolution-from-runsettings + deployment app-domain paths): 15/15 green.Verification
build.cmd -c Debuggreen across all TFMs (net462/net8.0/net9.0/UWP/WinUI), IDE0005 clean.PlatformServices.Desktop.IntegrationTests: 15, 0 failed.MSTestAdapter.PlatformServices.UnitTests: 897 (net8.0) / 935 (net462), 0 failed.MSTestAdapter.UnitTests: 21, 0 failed.Remaining after this
3 files with real deps:
TestDeployment(SuspendCodeCoverage, netfx),TestSourceHost/TestSourceHandler(AssemblyHelper+EqtTrace) — 6e-4c. Then Phase 7 drops thePackageReference+ adds the zero-ObjectModel-reference guard test (theAssemblyResolver+ this file's string literals don't block it).Stacking
… #9624→6e-3b #9626→6e-4a #9627→ 6e-4b (this), ontodev/amauryleve/vstest-decoupling-base. Base = 6e-4a branch (dev/amauryleve/vstest-decoupling-settings-utils). Do not rebase/reset the base.