From c36e779068ac198579757b971075e6a368e6a16a Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Mon, 27 Apr 2026 17:40:36 +0200 Subject: [PATCH 01/13] 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 | 2 +- src/testhost.x86/app.config | 4 +- src/vstest.console/app.config | 2 +- .../DistributedTestAgentScenarioTests.cs | 120 ++++++++++++++++++ .../TestAssets/DtaLikeHost/DtaLikeHost.csproj | 71 +++++++++++ test/TestAssets/DtaLikeHost/Program.cs | 53 ++++++++ 7 files changed, 270 insertions(+), 4 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..66d25d9695 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -26,7 +26,7 @@ - + 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..289ad7b472 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -31,7 +31,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..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 40c8398c6ff6fa71dd2a014b1a5abdca7b6e560f Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 16:41:22 +0200 Subject: [PATCH 02/13] 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. Fixes #15718 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.targets | 13 ++- ...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 | 100 ++++++++++++++++++ 10 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs diff --git a/Directory.Build.targets b/Directory.Build.targets index f0c700ee2e..6d96423635 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -4,7 +4,18 @@ <_NetFrameworkHostedCompilersVersion Condition="'$(_NetFrameworkHostedCompilersVersion)' == ''">4.11.0-3.24280.3 - + + + $(DefineConstants);IS_VSTEST_REPO + + $(MSBuildWarningsAsMessages);MSB3277 + + 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 d464fe4c5d..66d25d9695 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -26,7 +26,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 ff5d13c675..eafd00ce5b 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -39,7 +39,7 @@ - + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 115518cc51..289ad7b472 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -31,7 +31,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..a54858b130 --- /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.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 bc822a3ff7551e7bf237f55bdac4832619e5bf81 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 16:56:51 +0200 Subject: [PATCH 03/13] Remove old ExcludeAssets remnant, fix corrupted app.config XML Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.targets | 22 ---------------------- src/testhost.x86/app.config | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 5e6b17e1f1..6d96423635 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -81,26 +81,4 @@ - - - - - $(PkgSystem_Collections_Immutable)\lib\netstandard2.0\System.Collections.Immutable.dll - - - 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 @@ - + From cce710f4ab9a9b5dd585be87e633a854886b5217 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 17:43:06 +0200 Subject: [PATCH 04/13] 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 d1619750965b4ece2b13414f2848c18f54bd8d17 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 18:03:51 +0200 Subject: [PATCH 05/13] 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 96ecd541d2422ee28bd15d70766ea7390d851774 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 18:25:42 +0200 Subject: [PATCH 06/13] 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 | 2 +- src/testhost.x86/app.config | 2 +- src/vstest.console/app.config | 2 +- .../DistributedTestAgentScenarioTests.cs | 101 ++++++++++++++++++ 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs diff --git a/eng/Versions.props b/eng/Versions.props index 65d997fd1f..f3ef3e1262 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.5.5 8.0.0 18.0.0-preview-1-10911-061 diff --git a/src/datacollector/app.config b/src/datacollector/app.config index 66d25d9695..3c76fd609c 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -26,7 +26,7 @@ - + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index eafd00ce5b..6281e81f30 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -39,7 +39,7 @@ - + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 289ad7b472..b8df69e061 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -31,7 +31,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..972a6d6f2c --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DistributedTestAgentScenarioTests.cs @@ -0,0 +1,101 @@ +// 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 7e40bd7a1e54310e0de7bbf533d9bc8541dc6263 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Tue, 28 Apr 2026 21:42:38 +0200 Subject: [PATCH 07/13] Add System.Memory binding redirect to testhost.x86, datacollector, and vstest.console 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, the CLR cannot resolve the version 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 | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/datacollector/app.config b/src/datacollector/app.config index 3c76fd609c..cae0df0760 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 6281e81f30..d3339bf1d1 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -37,6 +37,10 @@ + + + + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index b8df69e061..9f334823ba 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -40,7 +40,7 @@ - + From 6c9afc18a7fafeb406f6d6d1f67cb8aafafb152e Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 09:45:29 +0200 Subject: [PATCH 08/13] Add System.Buffers binding redirect and fix System.Text.Json redirect 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. Also fix System.Text.Json redirect in vstest.console to point to 6.0.0.0 (the actual shipped DLL version) instead of 6.0.0.11. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/datacollector/app.config | 6 ++++++ src/testhost.x86/app.config | 6 ++++++ src/vstest.console/app.config | 7 ++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/datacollector/app.config b/src/datacollector/app.config index cae0df0760..4f71ae5b59 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -28,6 +28,12 @@ + + + + + + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index d3339bf1d1..1611456f6c 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -41,6 +41,12 @@ + + + + + + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 9f334823ba..18ddf3f48c 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -43,6 +43,11 @@ + + + + + @@ -55,7 +60,7 @@ - + From bfdff1d6c878288d5148729c388af76763c2013e Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 11:39:46 +0200 Subject: [PATCH 09/13] 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 8 test host runtime only ships SCI 8.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(); } } From dccd21cf6a238da7a0bf5037aace0f2568a7346e Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 15:38:56 +0200 Subject: [PATCH 10/13] Fix Unsafe binding redirect version 6.0.0.0 -> 6.0.3.0 The actual System.Runtime.CompilerServices.Unsafe DLL shipped in the nupkg has assembly version 6.0.3.0 (from SCI 10.0.0 transitive deps), but the binding redirect was still targeting 6.0.0.0, causing FileLoadException in testhost and other processes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/datacollector/app.config | 2 +- src/testhost.x86/app.config | 2 +- src/vstest.console/app.config | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datacollector/app.config b/src/datacollector/app.config index 4f71ae5b59..3b5c8143b9 100644 --- a/src/datacollector/app.config +++ b/src/datacollector/app.config @@ -22,7 +22,7 @@ - + diff --git a/src/testhost.x86/app.config b/src/testhost.x86/app.config index 1611456f6c..84cb676f80 100644 --- a/src/testhost.x86/app.config +++ b/src/testhost.x86/app.config @@ -35,7 +35,7 @@ - + diff --git a/src/vstest.console/app.config b/src/vstest.console/app.config index 18ddf3f48c..de2fe51c34 100644 --- a/src/vstest.console/app.config +++ b/src/vstest.console/app.config @@ -27,7 +27,7 @@ - + From 6817a0f8184f971ec4079f5a065d6dfa159e2ae1 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 15:53:09 +0200 Subject: [PATCH 11/13] Retrigger CI - pool may have recovered Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 2219b2482b1b641ae9268de09bba12013b63496f Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 15:57:16 +0200 Subject: [PATCH 12/13] Retrigger CI (pool stalled 24h+) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From d05f191f75270436e06c1b79ec08ba75ca30b1e5 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Wed, 29 Apr 2026 16:04:09 +0200 Subject: [PATCH 13/13] Retrigger CI (pool stalled 36h+) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>