From 675607028530c3944660661d8f41d2aa906abc69 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Mon, 27 Apr 2026 17:40:36 +0200 Subject: [PATCH 1/9] Fix SCI binding for DTA-like hosts without binding redirects Force .NET Framework product projects to compile against the netstandard2.0 build of System.Collections.Immutable (AssemblyVersion 9.0.0.0) instead of the net462 build (AssemblyVersion 9.0.0.11). SCI 9.0.11 introduced an AV divergence between the two TFMs; the nupkg ships the netstandard2.0 DLL, so compiled metadata must reference 9.0.0.0 for consumers without binding redirects (e.g. Azure DevOps Distributed Test Agent) to avoid FileLoadException. - Directory.Build.targets: ExcludeAssets=compile on SCI PackageReference + explicit Reference to netstandard2.0 DLL for .NET Framework product projects - Extend binding redirect oldVersion to cover 9.0.0.11 - Add DtaLikeHost test asset and acceptance test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.targets | 22 ++++ src/datacollector/app.config | 4 +- src/testhost.x86/app.config | 4 +- src/vstest.console/app.config | 4 +- .../DistributedTestAgentScenarioTests.cs | 120 ++++++++++++++++++ .../TestAssets/DtaLikeHost/DtaLikeHost.csproj | 71 +++++++++++ test/TestAssets/DtaLikeHost/Program.cs | 53 ++++++++ 7 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs create mode 100644 test/TestAssets/DtaLikeHost/DtaLikeHost.csproj create mode 100644 test/TestAssets/DtaLikeHost/Program.cs diff --git a/Directory.Build.targets b/Directory.Build.targets index f0c700ee2e..e78b7a2d2f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -70,4 +70,26 @@ + + + + + $(PkgSystem_Collections_Immutable)\lib\netstandard2.0\System.Collections.Immutable.dll + + + diff --git a/src/datacollector/app.config b/src/datacollector/app.config index d464fe4c5d..33f44bf7c6 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -26,10 +26,10 @@ - + - + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index ff5d13c675..fed0fdef5c 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -39,10 +39,10 @@ - + - + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 115518cc51..940159746e 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -31,10 +31,10 @@ - + - + diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs new file mode 100644 index 0000000000..b73f9e7a64 --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +/// +/// Reproduces the binding-redirect scenario experienced by Azure DevOps' Distributed +/// Test Agent (DTAExecutionHost) and any Visual Studio host that picks up +/// Microsoft.VisualStudio.TestPlatform.Common.dll without the in-box +/// vstest.console.exe.config binding redirects. +/// +/// The test loads Common.dll inside a net472 host that has no binding +/// redirects in its app.config and calls +/// , +/// which triggers FastFilter.Builder and forces +/// System.Collections.Immutable / System.Reflection.Metadata to load. +/// +/// It runs the scenario twice: +/// 1. Against the Microsoft.TestPlatform nupkg's +/// tools/net462/Common7/IDE/Extensions/TestPlatform/ layout (as DTA consumes it). +/// 2. Against the flat layout of the Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI +/// VSIX (as Visual Studio consumes it). +/// +/// Regression guard: if Common.dll's compiled metadata references for SCI/SRM drift +/// away from the versions we ship next to it, the test fails with the same +/// FileLoadException customers see. Both layouts must stay self-consistent. +/// +[TestClass] +public class DistributedTestAgentScenarioTests : AcceptanceTestBase +{ + [TestMethod] + [TestCategory("Windows-Review")] + public void LoadingCommonDllFromMicrosoftTestPlatformPackageWithoutBindingRedirectsDoesNotThrow() + { + // Nupkg layout: DTA-style consumption of the Microsoft.TestPlatform nupkg. + RunDtaLikeHost(toolsDirOverride: null); + } + + [TestMethod] + [TestCategory("Windows-Review")] + public void LoadingCommonDllFromCliV2VsixLayoutWithoutBindingRedirectsDoesNotThrow() + { + // VSIX layout: flat folder with Common.dll + SCI + SRM at the root, as shipped + // in Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix and consumed by + // Visual Studio. The VSIX is unzipped into PublishDirectory by Build.cs. + var extractedVsixDir = Path.Combine( + IntegrationTestEnvironment.PublishDirectory, + Path.GetFileName(IntegrationTestEnvironment.LocalVsixInsertion)); + + Assert.IsTrue( + Directory.Exists(extractedVsixDir), + $"Extracted VSIX directory not found at '{extractedVsixDir}'. " + + "Build.cs is expected to unzip the V2.CLI VSIX before acceptance tests run."); + + Assert.IsTrue( + File.Exists(Path.Combine(extractedVsixDir, "Microsoft.VisualStudio.TestPlatform.Common.dll")), + $"Expected Common.dll at the root of the extracted VSIX ('{extractedVsixDir}')."); + + RunDtaLikeHost(toolsDirOverride: extractedVsixDir); + } + + private void RunDtaLikeHost(string? toolsDirOverride) + { + var projectPath = GetIsolatedTestAsset("DtaLikeHost.csproj", "net472"); + var workingDir = Path.GetDirectoryName(projectPath)!; + + var dotnetPath = GetPatchedDotnetPath(); + + var buildArgs = + $@"build ""{projectPath}"" -c {IntegrationTestEnvironment.BuildConfiguration} " + + $@"/p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion} " + + @"/nodeReuse:false"; + + if (toolsDirOverride is not null) + { + buildArgs += $@" /p:TestPlatformToolsDirOverride=""{toolsDirOverride}"""; + } + + ExecuteApplication(dotnetPath, buildArgs, out var buildOut, out var buildErr, out var buildExit, workingDirectory: workingDir); + + Assert.AreEqual( + 0, + buildExit, + $"dotnet build of DtaLikeHost failed (exit {buildExit}).\nSTDOUT:\n{buildOut}\nSTDERR:\n{buildErr}"); + + var exePath = Path.Combine( + workingDir, + "artifacts", "bin", "TestAssets", "DtaLikeHost", + IntegrationTestEnvironment.BuildConfiguration, + "net472", + "DtaLikeHost.exe"); + + Assert.IsTrue(File.Exists(exePath), $"Expected DtaLikeHost.exe at '{exePath}'."); + + ExecuteApplication(exePath, args: null, out var runOut, out var runErr, out var runExit); + + Assert.AreEqual( + 0, + runExit, + "DtaLikeHost.exe exited non-zero, which means Common.dll's compiled metadata " + + "references for System.Collections.Immutable / System.Reflection.Metadata do " + + "not match the versions shipped next to it. DTA-style hosts (no binding " + + "redirects) will FileLoadException on FastFilter.Builder.\n" + + $"Tools dir: {toolsDirOverride ?? ""}\n" + + $"STDOUT:\n{runOut}\nSTDERR:\n{runErr}"); + + Assert.Contains("OK - no binding exception.", runOut); + } + + private static string GetPatchedDotnetPath() + { + var executable = OSUtils.IsWindows ? "dotnet.exe" : "dotnet"; + return Path.GetFullPath(Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, "artifacts", "tmp", ".dotnet", executable)); + } +} diff --git a/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj b/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj new file mode 100644 index 0000000000..cf11b4e281 --- /dev/null +++ b/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj @@ -0,0 +1,71 @@ + + + + + + DtaLikeHost + net472 + Exe + + false + false + true + + false + false + + + + + + + + + <_TestPlatformToolsDir>$(PkgMicrosoft_TestPlatform)\tools\net462\Common7\IDE\Extensions\TestPlatform + + + + + <_TestPlatformToolsDir>$(TestPlatformToolsDirOverride) + + + + + + $(_TestPlatformToolsDir)\Microsoft.VisualStudio.TestPlatform.Common.dll + true + + + $(_TestPlatformToolsDir)\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + true + + + + + + + + diff --git a/test/TestAssets/DtaLikeHost/Program.cs b/test/TestAssets/DtaLikeHost/Program.cs new file mode 100644 index 0000000000..7999bed8a5 --- /dev/null +++ b/test/TestAssets/DtaLikeHost/Program.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; + +using Microsoft.VisualStudio.TestPlatform.Common.Filtering; + +namespace DtaLikeHost; + +internal static class Program +{ + private static int Main() + { + // Report what Common.dll expects and what we ship next to it, so the mismatch + // (or agreement) is visible in the console output regardless of whether the + // CLR actually fails to bind. + var commonAsm = typeof(FilterExpressionWrapper).Assembly; + Console.WriteLine($"Common.dll path: {commonAsm.Location}"); + Console.WriteLine($"Common.dll version: {commonAsm.GetName().Version}"); + foreach (var r in commonAsm.GetReferencedAssemblies()) + { + if (r.Name == "System.Collections.Immutable" || r.Name == "System.Reflection.Metadata") + { + Console.WriteLine($" Common.dll references {r.Name}, Version={r.Version}"); + } + } + + try + { + // A simple equality filter produces a FastFilter, which triggers + // FastFilter.Builder.ctor -> ImmutableDictionary.CreateBuilder(...) + // -> forces the CLR to resolve System.Collections.Immutable at the version + // baked into Common.dll's metadata. + var wrapper = new FilterExpressionWrapper("TestCategory=Foo"); + Console.WriteLine($"FilterExpressionWrapper constructed: FilterString='{wrapper.FilterString}', ParseError='{wrapper.ParseError}'"); + + // Reflect on the private FastFilter field to prove it was actually built. + var fastFilterField = typeof(FilterExpressionWrapper).GetField("FastFilter", BindingFlags.Instance | BindingFlags.NonPublic); + var fastFilter = fastFilterField?.GetValue(wrapper); + Console.WriteLine($"FastFilter built: {fastFilter is not null}"); + } + catch (Exception ex) + { + Console.Error.WriteLine("REPRO HIT: exception constructing FilterExpressionWrapper:"); + Console.Error.WriteLine(ex); + return 1; + } + + Console.WriteLine("OK - no binding exception."); + return 0; + } +} From b94751602c1d7646aad7529b07f99763f39054f8 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 16:44:29 +0200 Subject: [PATCH 2/9] Fix SCI binding failure in DTA hosts without binding redirects Add explicit System.Collections.Immutable PackageReference to CoreUtilities and ObjectModel for non-.NETCoreApp targets. This ensures product assemblies compile against SCI 9.0.0.0, matching the shipped DLL. Extend SCI binding redirect to cover net462 AV 9.0.0.11. Suppress MSB3277 for .NETCoreApp targets. Fix MSBuildWarningsAsMessages inheritance in packaging projects. Fix corrupted XML in app.config files (missing space). Remove old DtaLikeHost test asset, replace with nupkg-layout test. Fixes #15718 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.targets | 31 ++-- ...icrosoft.TestPlatform.CoreUtilities.csproj | 1 + .../Microsoft.TestPlatform.ObjectModel.csproj | 1 + src/datacollector/app.config | 2 +- .../Microsoft.TestPlatform.CLI.csproj | 2 +- .../Microsoft.TestPlatform.Portable.csproj | 2 +- .../Microsoft.TestPlatform.csproj | 2 +- src/testhost.x86/app.config | 2 +- src/vstest.console/app.config | 2 +- .../DistributedTestAgentScenarioTests.cs | 146 ++++++++---------- .../TestAssets/DtaLikeHost/DtaLikeHost.csproj | 71 --------- test/TestAssets/DtaLikeHost/Program.cs | 53 ------- 12 files changed, 81 insertions(+), 234 deletions(-) delete mode 100644 test/TestAssets/DtaLikeHost/DtaLikeHost.csproj delete mode 100644 test/TestAssets/DtaLikeHost/Program.cs diff --git a/Directory.Build.targets b/Directory.Build.targets index e78b7a2d2f..7c2399e44e 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -5,6 +5,16 @@ <_NetFrameworkHostedCompilersVersion Condition="'$(_NetFrameworkHostedCompilersVersion)' == ''">4.11.0-3.24280.3 + + + $(MSBuildWarningsAsMessages);MSB3277 + + @@ -70,26 +80,5 @@ - - - - - $(PkgSystem_Collections_Immutable)\lib\netstandard2.0\System.Collections.Immutable.dll - - diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj index 866225a77c..b08654b168 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj +++ b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index 1675d4cb00..c3ed0ec398 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -37,6 +37,7 @@ + diff --git a/src/datacollector/app.config b/src/datacollector/app.config index 33f44bf7c6..66d25d9695 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -29,7 +29,7 @@ - + diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj index 696cb68cbf..f3ad7825d0 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj @@ -37,7 +37,7 @@ Sometimes NU1702 is not suppressed correctly, so force reducing severity of the warning. See https://github.com/NuGet/Home/issues/9147 --> - NU1702 + $(MSBuildWarningsAsMessages);NU1702 diff --git a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj index 33fde7df1b..f17f680575 100644 --- a/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj +++ b/src/package/Microsoft.TestPlatform.Portable/Microsoft.TestPlatform.Portable.csproj @@ -12,7 +12,7 @@ Sometimes NU1702 is not suppressed correctly, so force reducing severity of the warning. See https://github.com/NuGet/Home/issues/9147 --> - NU1702 + $(MSBuildWarningsAsMessages);NU1702 diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj index 540e0910ea..6739984bbd 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj @@ -11,7 +11,7 @@ Sometimes NU1702 is not suppressed correctly, so force reducing severity of the warning. See https://github.com/NuGet/Home/issues/9147 --> - NU1702;NETSDK1023 + $(MSBuildWarningsAsMessages);NU1702;NETSDK1023 diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index fed0fdef5c..eafd00ce5b 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -42,7 +42,7 @@ - + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 940159746e..289ad7b472 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -34,7 +34,7 @@ - + diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs index b73f9e7a64..a54858b130 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs @@ -1,7 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -9,112 +13,88 @@ namespace Microsoft.TestPlatform.AcceptanceTests; /// -/// Reproduces the binding-redirect scenario experienced by Azure DevOps' Distributed -/// Test Agent (DTAExecutionHost) and any Visual Studio host that picks up -/// Microsoft.VisualStudio.TestPlatform.Common.dll without the in-box -/// vstest.console.exe.config binding redirects. -/// -/// The test loads Common.dll inside a net472 host that has no binding -/// redirects in its app.config and calls -/// , -/// which triggers FastFilter.Builder and forces -/// System.Collections.Immutable / System.Reflection.Metadata to load. -/// -/// It runs the scenario twice: -/// 1. Against the Microsoft.TestPlatform nupkg's -/// tools/net462/Common7/IDE/Extensions/TestPlatform/ layout (as DTA consumes it). -/// 2. Against the flat layout of the Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI -/// VSIX (as Visual Studio consumes it). -/// -/// Regression guard: if Common.dll's compiled metadata references for SCI/SRM drift -/// away from the versions we ship next to it, the test fails with the same -/// FileLoadException customers see. Both layouts must stay self-consistent. +/// Tests that the DTA (Distributed Test Agent) scenario works: a .NET Framework host +/// loads Microsoft.TestPlatform.Common.dll without binding redirects. The shipped +/// System.Collections.Immutable DLL must match the assembly version referenced by +/// the compiled product assemblies. /// [TestClass] public class DistributedTestAgentScenarioTests : AcceptanceTestBase { + /// + /// Validates that the Microsoft.TestPlatform nupkg layout (used by DTA/VSTest task) + /// contains a System.Collections.Immutable whose assembly version matches what + /// Common.dll was compiled against. + /// [TestMethod] - [TestCategory("Windows-Review")] - public void LoadingCommonDllFromMicrosoftTestPlatformPackageWithoutBindingRedirectsDoesNotThrow() + public void NupkgLayout_CommonDll_CanLoadSciWithoutBindingRedirects() { - // Nupkg layout: DTA-style consumption of the Microsoft.TestPlatform nupkg. - RunDtaLikeHost(toolsDirOverride: null); - } - - [TestMethod] - [TestCategory("Windows-Review")] - public void LoadingCommonDllFromCliV2VsixLayoutWithoutBindingRedirectsDoesNotThrow() - { - // VSIX layout: flat folder with Common.dll + SCI + SRM at the root, as shipped - // in Microsoft.VisualStudio.TestTools.TestPlatform.V2.CLI.vsix and consumed by - // Visual Studio. The VSIX is unzipped into PublishDirectory by Build.cs. - var extractedVsixDir = Path.Combine( + var testPlatformDir = Path.Combine( IntegrationTestEnvironment.PublishDirectory, - Path.GetFileName(IntegrationTestEnvironment.LocalVsixInsertion)); - - Assert.IsTrue( - Directory.Exists(extractedVsixDir), - $"Extracted VSIX directory not found at '{extractedVsixDir}'. " + - "Build.cs is expected to unzip the V2.CLI VSIX before acceptance tests run."); + $"Microsoft.TestPlatform.{IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}.nupkg", + "tools", "net462", "Common7", "IDE", "Extensions", "TestPlatform"); - Assert.IsTrue( - File.Exists(Path.Combine(extractedVsixDir, "Microsoft.VisualStudio.TestPlatform.Common.dll")), - $"Expected Common.dll at the root of the extracted VSIX ('{extractedVsixDir}')."); - - RunDtaLikeHost(toolsDirOverride: extractedVsixDir); + ValidateDtaScenario(testPlatformDir, "nupkg"); } - private void RunDtaLikeHost(string? toolsDirOverride) + /// + /// Validates that the V2.CLI VSIX layout also has matching SCI assembly versions. + /// + [TestMethod] + public void VsixLayout_CommonDll_CanLoadSciWithoutBindingRedirects() { - var projectPath = GetIsolatedTestAsset("DtaLikeHost.csproj", "net472"); - var workingDir = Path.GetDirectoryName(projectPath)!; - - var dotnetPath = GetPatchedDotnetPath(); - - var buildArgs = - $@"build ""{projectPath}"" -c {IntegrationTestEnvironment.BuildConfiguration} " + - $@"/p:PackageVersion={IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion} " + - @"/nodeReuse:false"; + var vsixDir = Path.GetDirectoryName(IntegrationTestEnvironment.LocalVsixInsertion); + if (vsixDir is null || !Directory.Exists(vsixDir)) + { + Assert.Inconclusive("VSIX directory not found. Build with -pack to produce it."); + } - if (toolsDirOverride is not null) + var vsixExtractDir = Path.Combine(IntegrationTestEnvironment.ArtifactsTempDirectory, "vsix-extracted"); + if (!Directory.Exists(vsixExtractDir)) { - buildArgs += $@" /p:TestPlatformToolsDirOverride=""{toolsDirOverride}"""; + System.IO.Compression.ZipFile.ExtractToDirectory(IntegrationTestEnvironment.LocalVsixInsertion, vsixExtractDir); } - ExecuteApplication(dotnetPath, buildArgs, out var buildOut, out var buildErr, out var buildExit, workingDirectory: workingDir); + ValidateDtaScenario(vsixExtractDir, "VSIX"); + } - Assert.AreEqual( - 0, - buildExit, - $"dotnet build of DtaLikeHost failed (exit {buildExit}).\nSTDOUT:\n{buildOut}\nSTDERR:\n{buildErr}"); + private static void ValidateDtaScenario(string testPlatformDir, string layoutName) + { + Assert.IsTrue(Directory.Exists(testPlatformDir), $"{layoutName} directory not found: {testPlatformDir}"); - var exePath = Path.Combine( - workingDir, - "artifacts", "bin", "TestAssets", "DtaLikeHost", - IntegrationTestEnvironment.BuildConfiguration, - "net472", - "DtaLikeHost.exe"); + var commonDll = Path.Combine(testPlatformDir, "Microsoft.TestPlatform.Common.dll"); + var sciDll = Path.Combine(testPlatformDir, "System.Collections.Immutable.dll"); - Assert.IsTrue(File.Exists(exePath), $"Expected DtaLikeHost.exe at '{exePath}'."); + Assert.IsTrue(File.Exists(commonDll), $"Common.dll not found in {layoutName} layout: {commonDll}"); + Assert.IsTrue(File.Exists(sciDll), $"System.Collections.Immutable.dll not found in {layoutName} layout: {sciDll}"); - ExecuteApplication(exePath, args: null, out var runOut, out var runErr, out var runExit); + var sciVersion = System.Reflection.AssemblyName.GetAssemblyName(sciDll).Version; + var sciRefVersion = GetReferencedAssemblyVersion(commonDll, "System.Collections.Immutable"); + Assert.IsNotNull(sciRefVersion, $"Common.dll in {layoutName} layout does not reference System.Collections.Immutable"); + + // For DTA scenario (no binding redirects), these MUST match exactly. Assert.AreEqual( - 0, - runExit, - "DtaLikeHost.exe exited non-zero, which means Common.dll's compiled metadata " + - "references for System.Collections.Immutable / System.Reflection.Metadata do " + - "not match the versions shipped next to it. DTA-style hosts (no binding " + - "redirects) will FileLoadException on FastFilter.Builder.\n" + - $"Tools dir: {toolsDirOverride ?? ""}\n" + - $"STDOUT:\n{runOut}\nSTDERR:\n{runErr}"); - - Assert.Contains("OK - no binding exception.", runOut); + sciRefVersion, + sciVersion, + $"{layoutName} layout: Common.dll references SCI {sciRefVersion} but shipped DLL is {sciVersion}. " + + $"DTA hosts without binding redirects will fail with FileLoadException."); } - private static string GetPatchedDotnetPath() + private static Version? GetReferencedAssemblyVersion(string assemblyPath, string referenceName) { - var executable = OSUtils.IsWindows ? "dotnet.exe" : "dotnet"; - return Path.GetFullPath(Path.Combine(IntegrationTestEnvironment.RepoRootDirectory, "artifacts", "tmp", ".dotnet", executable)); + using var stream = File.OpenRead(assemblyPath); + using var peReader = new PEReader(stream); + var mdReader = peReader.GetMetadataReader(); + foreach (var handle in mdReader.AssemblyReferences) + { + var asmRef = mdReader.GetAssemblyReference(handle); + if (mdReader.GetString(asmRef.Name) == referenceName) + { + return asmRef.Version; + } + } + + return null; } } diff --git a/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj b/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj deleted file mode 100644 index cf11b4e281..0000000000 --- a/test/TestAssets/DtaLikeHost/DtaLikeHost.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - DtaLikeHost - net472 - Exe - - false - false - true - - false - false - - - - - - - - - <_TestPlatformToolsDir>$(PkgMicrosoft_TestPlatform)\tools\net462\Common7\IDE\Extensions\TestPlatform - - - - - <_TestPlatformToolsDir>$(TestPlatformToolsDirOverride) - - - - - - $(_TestPlatformToolsDir)\Microsoft.VisualStudio.TestPlatform.Common.dll - true - - - $(_TestPlatformToolsDir)\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll - true - - - - - - - - diff --git a/test/TestAssets/DtaLikeHost/Program.cs b/test/TestAssets/DtaLikeHost/Program.cs deleted file mode 100644 index 7999bed8a5..0000000000 --- a/test/TestAssets/DtaLikeHost/Program.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Reflection; - -using Microsoft.VisualStudio.TestPlatform.Common.Filtering; - -namespace DtaLikeHost; - -internal static class Program -{ - private static int Main() - { - // Report what Common.dll expects and what we ship next to it, so the mismatch - // (or agreement) is visible in the console output regardless of whether the - // CLR actually fails to bind. - var commonAsm = typeof(FilterExpressionWrapper).Assembly; - Console.WriteLine($"Common.dll path: {commonAsm.Location}"); - Console.WriteLine($"Common.dll version: {commonAsm.GetName().Version}"); - foreach (var r in commonAsm.GetReferencedAssemblies()) - { - if (r.Name == "System.Collections.Immutable" || r.Name == "System.Reflection.Metadata") - { - Console.WriteLine($" Common.dll references {r.Name}, Version={r.Version}"); - } - } - - try - { - // A simple equality filter produces a FastFilter, which triggers - // FastFilter.Builder.ctor -> ImmutableDictionary.CreateBuilder(...) - // -> forces the CLR to resolve System.Collections.Immutable at the version - // baked into Common.dll's metadata. - var wrapper = new FilterExpressionWrapper("TestCategory=Foo"); - Console.WriteLine($"FilterExpressionWrapper constructed: FilterString='{wrapper.FilterString}', ParseError='{wrapper.ParseError}'"); - - // Reflect on the private FastFilter field to prove it was actually built. - var fastFilterField = typeof(FilterExpressionWrapper).GetField("FastFilter", BindingFlags.Instance | BindingFlags.NonPublic); - var fastFilter = fastFilterField?.GetValue(wrapper); - Console.WriteLine($"FastFilter built: {fastFilter is not null}"); - } - catch (Exception ex) - { - Console.Error.WriteLine("REPRO HIT: exception constructing FilterExpressionWrapper:"); - Console.Error.WriteLine(ex); - return 1; - } - - Console.WriteLine("OK - no binding exception."); - return 0; - } -} From 82d334614c6e0eb5d8c923e9dc760aed30e45866 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 17:43:06 +0200 Subject: [PATCH 3/9] Fix SCI condition to .NETFramework only The previous condition '!= .NETCoreApp' included netstandard2.0, which made netstandard2.0 assemblies reference SCI 9.0.0.0. This breaks on .NET 8 where the shared framework only has SCI 8.0.0.0. Change to '== .NETFramework' so only net462 builds get the explicit SCI reference (covered by binding redirects), while netstandard2.0 keeps SCI 8.0.0.0 from the SRM transitive dependency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.TestPlatform.CoreUtilities.csproj | 2 +- .../Microsoft.TestPlatform.ObjectModel.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj index b08654b168..6776a4de05 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj +++ b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index c3ed0ec398..0d20b23a19 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -37,7 +37,7 @@ - + From 4f9bfef00ed43ab9058a9707adce7cab2e91dd82 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 18:03:52 +0200 Subject: [PATCH 4/9] Remove DTA acceptance test The DTA scenario (loading SCI without binding redirects) cannot be fully fixed with SCI 9.0.11 due to assembly version divergence between net462 (9.0.0.11) and netstandard2.0 (9.0.0.0). This will be tracked in a separate issue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DistributedTestAgentScenarioTests.cs | 100 ------------------ 1 file changed, 100 deletions(-) delete mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs deleted file mode 100644 index a54858b130..0000000000 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.IO; -using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; - -using Microsoft.TestPlatform.TestUtilities; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.TestPlatform.AcceptanceTests; - -/// -/// Tests that the DTA (Distributed Test Agent) scenario works: a .NET Framework host -/// loads Microsoft.TestPlatform.Common.dll without binding redirects. The shipped -/// System.Collections.Immutable DLL must match the assembly version referenced by -/// the compiled product assemblies. -/// -[TestClass] -public class DistributedTestAgentScenarioTests : AcceptanceTestBase -{ - /// - /// Validates that the Microsoft.TestPlatform nupkg layout (used by DTA/VSTest task) - /// contains a System.Collections.Immutable whose assembly version matches what - /// Common.dll was compiled against. - /// - [TestMethod] - public void NupkgLayout_CommonDll_CanLoadSciWithoutBindingRedirects() - { - var testPlatformDir = Path.Combine( - IntegrationTestEnvironment.PublishDirectory, - $"Microsoft.TestPlatform.{IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}.nupkg", - "tools", "net462", "Common7", "IDE", "Extensions", "TestPlatform"); - - ValidateDtaScenario(testPlatformDir, "nupkg"); - } - - /// - /// Validates that the V2.CLI VSIX layout also has matching SCI assembly versions. - /// - [TestMethod] - public void VsixLayout_CommonDll_CanLoadSciWithoutBindingRedirects() - { - var vsixDir = Path.GetDirectoryName(IntegrationTestEnvironment.LocalVsixInsertion); - if (vsixDir is null || !Directory.Exists(vsixDir)) - { - Assert.Inconclusive("VSIX directory not found. Build with -pack to produce it."); - } - - var vsixExtractDir = Path.Combine(IntegrationTestEnvironment.ArtifactsTempDirectory, "vsix-extracted"); - if (!Directory.Exists(vsixExtractDir)) - { - System.IO.Compression.ZipFile.ExtractToDirectory(IntegrationTestEnvironment.LocalVsixInsertion, vsixExtractDir); - } - - ValidateDtaScenario(vsixExtractDir, "VSIX"); - } - - private static void ValidateDtaScenario(string testPlatformDir, string layoutName) - { - Assert.IsTrue(Directory.Exists(testPlatformDir), $"{layoutName} directory not found: {testPlatformDir}"); - - var commonDll = Path.Combine(testPlatformDir, "Microsoft.TestPlatform.Common.dll"); - var sciDll = Path.Combine(testPlatformDir, "System.Collections.Immutable.dll"); - - Assert.IsTrue(File.Exists(commonDll), $"Common.dll not found in {layoutName} layout: {commonDll}"); - Assert.IsTrue(File.Exists(sciDll), $"System.Collections.Immutable.dll not found in {layoutName} layout: {sciDll}"); - - var sciVersion = System.Reflection.AssemblyName.GetAssemblyName(sciDll).Version; - var sciRefVersion = GetReferencedAssemblyVersion(commonDll, "System.Collections.Immutable"); - - Assert.IsNotNull(sciRefVersion, $"Common.dll in {layoutName} layout does not reference System.Collections.Immutable"); - - // For DTA scenario (no binding redirects), these MUST match exactly. - Assert.AreEqual( - sciRefVersion, - sciVersion, - $"{layoutName} layout: Common.dll references SCI {sciRefVersion} but shipped DLL is {sciVersion}. " + - $"DTA hosts without binding redirects will fail with FileLoadException."); - } - - private static Version? GetReferencedAssemblyVersion(string assemblyPath, string referenceName) - { - using var stream = File.OpenRead(assemblyPath); - using var peReader = new PEReader(stream); - var mdReader = peReader.GetMetadataReader(); - foreach (var handle in mdReader.AssemblyReferences) - { - var asmRef = mdReader.GetAssemblyReference(handle); - if (mdReader.GetString(asmRef.Name) == referenceName) - { - return asmRef.Version; - } - } - - return null; - } -} From 92787701b6ee477a620b846d516f82cb43752971 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 18:25:42 +0200 Subject: [PATCH 5/9] Bump SCI to 10.0.0 to fix AV divergence SCI 9.0.11 has different assembly versions for net462 (9.0.0.11) vs netstandard2.0 (9.0.0.0). DTA hosts without binding redirects need exact version match. SCI 10.0.0 has AV 10.0.0.0 for all TFMs. - Bump SystemCollectionsImmutableVersion to 10.0.0 - Update binding redirects to 10.0.0.0 - Restore DTA acceptance test with correct DLL name Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/Versions.props | 2 +- src/datacollector/app.config | 4 +- src/testhost.x86/app.config | 4 +- src/vstest.console/app.config | 6 +- .../DistributedTestAgentScenarioTests.cs | 100 ++++++++++++++++++ 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs diff --git a/eng/Versions.props b/eng/Versions.props index db80003e5b..ea2b6c80ff 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -70,7 +70,7 @@ <_MicrosoftVSSDKBuildToolsVersion_>17.14.2119 5.0.0 13.0.3 - 9.0.11 + 10.0.0 4.6.3 8.0.0 18.0.0-preview-1-10911-061 diff --git a/src/datacollector/app.config b/src/datacollector/app.config index 66d25d9695..dd30bfefed 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -22,11 +22,11 @@ - + - + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index eafd00ce5b..67bbfc1dd7 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -35,11 +35,11 @@ - + - + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 289ad7b472..f78da17c7f 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -27,11 +27,11 @@ - + - + @@ -40,7 +40,7 @@ - + diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs new file mode 100644 index 0000000000..ee21e5e4e5 --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +/// +/// Tests that the DTA (Distributed Test Agent) scenario works: a .NET Framework host +/// loads Microsoft.VisualStudio.TestPlatform.Common.dll without binding redirects. The shipped +/// System.Collections.Immutable DLL must match the assembly version referenced by +/// the compiled product assemblies. +/// +[TestClass] +public class DistributedTestAgentScenarioTests : AcceptanceTestBase +{ + /// + /// Validates that the Microsoft.TestPlatform nupkg layout (used by DTA/VSTest task) + /// contains a System.Collections.Immutable whose assembly version matches what + /// Common.dll was compiled against. + /// + [TestMethod] + public void NupkgLayout_CommonDll_CanLoadSciWithoutBindingRedirects() + { + var testPlatformDir = Path.Combine( + IntegrationTestEnvironment.PublishDirectory, + $"Microsoft.TestPlatform.{IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion}.nupkg", + "tools", "net462", "Common7", "IDE", "Extensions", "TestPlatform"); + + ValidateDtaScenario(testPlatformDir, "nupkg"); + } + + /// + /// Validates that the V2.CLI VSIX layout also has matching SCI assembly versions. + /// + [TestMethod] + public void VsixLayout_CommonDll_CanLoadSciWithoutBindingRedirects() + { + var vsixDir = Path.GetDirectoryName(IntegrationTestEnvironment.LocalVsixInsertion); + if (vsixDir is null || !Directory.Exists(vsixDir)) + { + Assert.Inconclusive("VSIX directory not found. Build with -pack to produce it."); + } + + var vsixExtractDir = Path.Combine(IntegrationTestEnvironment.ArtifactsTempDirectory, "vsix-extracted"); + if (!Directory.Exists(vsixExtractDir)) + { + System.IO.Compression.ZipFile.ExtractToDirectory(IntegrationTestEnvironment.LocalVsixInsertion, vsixExtractDir); + } + + ValidateDtaScenario(vsixExtractDir, "VSIX"); + } + + private static void ValidateDtaScenario(string testPlatformDir, string layoutName) + { + Assert.IsTrue(Directory.Exists(testPlatformDir), $"{layoutName} directory not found: {testPlatformDir}"); + + var commonDll = Path.Combine(testPlatformDir, "Microsoft.VisualStudio.TestPlatform.Common.dll"); + var sciDll = Path.Combine(testPlatformDir, "System.Collections.Immutable.dll"); + + Assert.IsTrue(File.Exists(commonDll), $"Common.dll not found in {layoutName} layout: {commonDll}"); + Assert.IsTrue(File.Exists(sciDll), $"System.Collections.Immutable.dll not found in {layoutName} layout: {sciDll}"); + + var sciVersion = System.Reflection.AssemblyName.GetAssemblyName(sciDll).Version; + var sciRefVersion = GetReferencedAssemblyVersion(commonDll, "System.Collections.Immutable"); + + Assert.IsNotNull(sciRefVersion, $"Common.dll in {layoutName} layout does not reference System.Collections.Immutable"); + + // For DTA scenario (no binding redirects), these MUST match exactly. + Assert.AreEqual( + sciRefVersion, + sciVersion, + $"{layoutName} layout: Common.dll references SCI {sciRefVersion} but shipped DLL is {sciVersion}. " + + $"DTA hosts without binding redirects will fail with FileLoadException."); + } + + private static Version? GetReferencedAssemblyVersion(string assemblyPath, string referenceName) + { + using var stream = File.OpenRead(assemblyPath); + using var peReader = new PEReader(stream); + var mdReader = peReader.GetMetadataReader(); + foreach (var handle in mdReader.AssemblyReferences) + { + var asmRef = mdReader.GetAssemblyReference(handle); + if (mdReader.GetString(asmRef.Name) == referenceName) + { + return asmRef.Version; + } + } + + return null; + } +} From f6468a9503c71801d465eb5073fd1c49dce393d5 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 21:42:38 +0200 Subject: [PATCH 6/9] Add System.Memory binding redirect to testhost.x86 and datacollector SCI 10.0.0 introduces a transitive dependency on System.Memory for net462. In Release builds, the shipped System.Memory.dll has assembly version 4.0.5.0, but the MSTest adapter loads against 4.0.1.2. Without a binding redirect in testhost.x86.exe.config and datacollector.exe.config, the CLR cannot resolve the version mismatch, causing FileNotFoundException in integration tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/datacollector/app.config | 4 ++++ src/testhost.x86/app.config | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/datacollector/app.config b/src/datacollector/app.config index dd30bfefed..c4758fbc25 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -24,6 +24,10 @@ + + + + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index 67bbfc1dd7..3eff2a8502 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -37,6 +37,10 @@ + + + + From 4822674aedc32f3b51a8598ec359b07f025839d5 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 09:45:02 +0200 Subject: [PATCH 7/9] Add System.Buffers binding redirect to all app.configs System.Memory 4.6.3 (transitive from SCI 10.0.0) depends on System.Buffers. In Release builds, System.Buffers.dll has AV 4.0.5.0, but MSTest adapter loads against 4.0.3.0. Without a redirect, the CLR cannot resolve the mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/datacollector/app.config | 4 ++++ src/testhost.x86/app.config | 4 ++++ src/vstest.console/app.config | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/datacollector/app.config b/src/datacollector/app.config index c4758fbc25..496f08688e 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -28,6 +28,10 @@ + + + + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index 3eff2a8502..6cda344d7d 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -41,6 +41,10 @@ + + + + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index f78da17c7f..4dcad84670 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -43,6 +43,11 @@ + + + + + From b90551a8656aae9e41655cbf3eadcd6cfe65a8bd Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 09:45:59 +0200 Subject: [PATCH 8/9] Fix System.Text.Json redirect to actual DLL version 6.0.0.0 The redirect was pointing to 6.0.0.11 but the shipped DLL is 6.0.0.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/app.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 4dcad84670..de2fe51c34 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -60,7 +60,7 @@ - + From 79a4ee717e6460d6ee28f4a0aacb1b531721fb3f Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 11:39:46 +0200 Subject: [PATCH 9/9] Fix TypesToLoadAttributeTests to use custom ALC for dependency resolution The test loads net462 extension DLLs which reference SCI 10.0.0.0, but the .NET 9 test host runtime only ships SCI 9.0.0.0. Assembly.LoadFrom cannot resolve the version mismatch. Use a custom AssemblyLoadContext that resolves dependencies from the extensions directory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DiscoveryTests.cs | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs index 3680b3c9bc..ef1817aeb7 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Loader; using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; @@ -106,15 +107,37 @@ public void TypesToLoadAttributeTests() } }; - foreach (var extension in extensionsToVerify.Keys) + // Use a custom AssemblyLoadContext so that dependencies (e.g. SCI 10.0.0) + // resolve from the extensions directory rather than the test host's runtime. + var parentDirectory = Path.GetDirectoryName(extensionsDirectory)!; + var alc = new AssemblyLoadContext("ExtensionTest", isCollectible: true); + alc.Resolving += (context, name) => { - var assemblyFile = Path.Combine(extensionsDirectory, extension); - var assembly = Assembly.LoadFrom(assemblyFile); + var path = Path.Combine(extensionsDirectory, name.Name + ".dll"); + if (File.Exists(path)) + return context.LoadFromAssemblyPath(path); + path = Path.Combine(parentDirectory, name.Name + ".dll"); + if (File.Exists(path)) + return context.LoadFromAssemblyPath(path); + return null; + }; + + try + { + foreach (var extension in extensionsToVerify.Keys) + { + var assemblyFile = Path.Combine(extensionsDirectory, extension); + var assembly = alc.LoadFromAssemblyPath(assemblyFile); - var expected = extensionsToVerify[extension]; - var actual = TypesToLoadUtilities.GetTypesToLoad(assembly).Select(i => i.FullName).ToArray(); + var expected = extensionsToVerify[extension]; + var actual = TypesToLoadUtilities.GetTypesToLoad(assembly).Select(i => i.FullName).ToArray(); - CollectionAssert.AreEquivalent(expected, actual, $"Specified types using TypesToLoadAttribute in \"{extension}\" assembly doesn't match the expected."); + CollectionAssert.AreEquivalent(expected, actual, $"Specified types using TypesToLoadAttribute in \"{extension}\" assembly doesn't match the expected."); + } + } + finally + { + alc.Unload(); } }