diff --git a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj
index b4792d9cad..23655fc099 100644
--- a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj
+++ b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj
@@ -82,6 +82,12 @@
that reference is suppressed above, so they are referenced explicitly here. -->
+
+
+
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/ApartmentStateSetting.cs b/src/Adapter/MSTestAdapter.PlatformServices/ApartmentStateSetting.cs
new file mode 100644
index 0000000000..4bb4f09f3a
--- /dev/null
+++ b/src/Adapter/MSTestAdapter.PlatformServices/ApartmentStateSetting.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
+
+///
+/// Apartment state parsed from the ExecutionThreadApartmentState run settings and the
+/// mstest:execution:executionApartmentState configuration value. This is the adapter's own neutral
+/// replacement for the former VSTest PlatformAbstractions.PlatformApartmentState enum.
+///
+internal enum ApartmentStateSetting
+{
+ // The member order is load-bearing: Enum.TryParse maps numeric-string run settings values ("0"/"1")
+ // by number, so MTA must stay 0 and STA must stay 1 to preserve parse compatibility with the former
+ // VSTest PlatformAbstractions.PlatformApartmentState enum. Do not reorder.
+ MTA,
+ STA,
+}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj b/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj
index 6a9e8820b1..97813d16d1 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj
+++ b/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj
@@ -36,7 +36,6 @@
-
+
+
+
+
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs b/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs
index 5723c35a44..8423ab05b6 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs
@@ -5,7 +5,6 @@
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
#endif
-using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
@@ -96,13 +95,13 @@ private static RunConfigurationSettings ToSettings(XmlReader reader)
{
case "EXECUTIONTHREADAPARTMENTSTATE":
{
- if (Enum.TryParse(reader.ReadInnerXml(), out PlatformApartmentState platformApartmentState))
+ if (Enum.TryParse(reader.ReadInnerXml(), out ApartmentStateSetting apartmentStateSetting))
{
- settings.ExecutionApartmentState = platformApartmentState switch
+ settings.ExecutionApartmentState = apartmentStateSetting switch
{
- PlatformApartmentState.STA => ApartmentState.STA,
- PlatformApartmentState.MTA => ApartmentState.MTA,
- _ => throw new NotSupportedException($"Platform apartment state '{platformApartmentState}' is not supported."),
+ ApartmentStateSetting.STA => ApartmentState.STA,
+ ApartmentStateSetting.MTA => ApartmentState.MTA,
+ _ => throw new NotSupportedException($"Platform apartment state '{apartmentStateSetting}' is not supported."),
};
}
@@ -131,13 +130,13 @@ internal static RunConfigurationSettings SetRunConfigurationSettingsFromConfig(I
// }
// }
string? apartmentStateValue = configuration["mstest:execution:executionApartmentState"];
- if (Enum.TryParse(apartmentStateValue, out PlatformApartmentState platformApartmentState))
+ if (Enum.TryParse(apartmentStateValue, out ApartmentStateSetting apartmentStateSetting))
{
- settings.ExecutionApartmentState = platformApartmentState switch
+ settings.ExecutionApartmentState = apartmentStateSetting switch
{
- PlatformApartmentState.STA => ApartmentState.STA,
- PlatformApartmentState.MTA => ApartmentState.MTA,
- _ => throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, platformApartmentState, "execution:executionApartmentState")),
+ ApartmentStateSetting.STA => ApartmentState.STA,
+ ApartmentStateSetting.MTA => ApartmentState.MTA,
+ _ => throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, apartmentStateSetting, "execution:executionApartmentState")),
};
}
diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs
index a2ce548535..ae97ba6ab2 100644
--- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs
+++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHandler.cs
@@ -110,7 +110,7 @@ public bool IsAssemblyReferenced(AssemblyName assemblyName, string source)
try
{
string? referenceAssemblyName = referenceAssembly.Name;
- byte[] referenceAssemblyPublicKeyToken = referenceAssembly.GetPublicKeyToken();
+ byte[]? referenceAssemblyPublicKeyToken = referenceAssembly.GetPublicKeyToken();
// ReflectionOnlyLoadFrom loads from the specified path only (no probing) and does not
// execute any code from the loaded assembly.
@@ -139,8 +139,20 @@ public bool IsAssemblyReferenced(AssemblyName assemblyName, string source)
}
}
- private static bool ArePublicKeyTokensEqual(byte[] left, byte[] right)
+ private static bool ArePublicKeyTokensEqual(byte[]? left, byte[]? right)
{
+ // GetPublicKeyToken() returns null (or an empty array) for unsigned assemblies.
+ // Treat two unsigned tokens as equal, and a signed vs. unsigned pair as unequal.
+ if (left is null || left.Length == 0)
+ {
+ return right is null || right.Length == 0;
+ }
+
+ if (right is null || right.Length == 0)
+ {
+ return false;
+ }
+
if (left.Length != right.Length)
{
return false;
diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/PlatformServices.Desktop.IntegrationTests.csproj b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/PlatformServices.Desktop.IntegrationTests.csproj
index de991b9157..0f8dac1571 100644
--- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/PlatformServices.Desktop.IntegrationTests.csproj
+++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/PlatformServices.Desktop.IntegrationTests.csproj
@@ -15,6 +15,9 @@
+
+
diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModelDecouplingTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModelDecouplingTests.cs
new file mode 100644
index 0000000000..16efa478fc
--- /dev/null
+++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModelDecouplingTests.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using AwesomeAssertions;
+
+using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
+
+using TestFramework.ForTestingMSTest;
+
+namespace MSTestAdapter.PlatformServices.UnitTests;
+
+public class ObjectModelDecouplingTests : TestContainer
+{
+ // Guards the platform-agnostic contract: the compiled MSTestAdapter.PlatformServices assembly must not
+ // reference the VSTest object model (Microsoft.VisualStudio.TestPlatform.ObjectModel) or any other
+ // Microsoft.*.TestPlatform.* assembly. All VSTest coupling lives in the MSTest.TestAdapter layer above it.
+ public void PlatformServicesAssemblyShouldNotReferenceAnyTestPlatformAssembly()
+ {
+ Assembly platformServices = typeof(TestSourceHandler).Assembly;
+
+ IEnumerable testPlatformReferences = platformServices
+ .GetReferencedAssemblies()
+ .Select(reference => reference.Name!)
+ .Where(name => name.IndexOf("TestPlatform", StringComparison.OrdinalIgnoreCase) >= 0);
+
+ testPlatformReferences.Should().BeEmpty(
+ "PlatformServices must stay platform-agnostic and must not reference the VSTest object model or any Microsoft.*.TestPlatform.* assembly");
+ }
+}
diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceTests.cs
index 461efbd2dd..2ea65db70a 100644
--- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceTests.cs
+++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceTests.cs
@@ -29,5 +29,20 @@ public class DesktopTestSourceTests : TestContainer
public void IsAssemblyReferencedShouldReturnTrueIfAnAssemblyIsReferencedInSource() => _testSource.IsAssemblyReferenced(typeof(Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute).Assembly.GetName(), Assembly.GetExecutingAssembly().Location).Should().BeTrue();
public void IsAssemblyReferencedShouldReturnFalseIfAnAssemblyIsNotReferencedInSource() => _testSource.IsAssemblyReferenced(new AssemblyName("foobar"), Assembly.GetExecutingAssembly().Location).Should().BeFalse();
+
+ // The source references the strong-named MSTest.TestFramework assembly. Passing an assembly name with the
+ // same simple name but no public key token (as an unsigned build would have) must not be considered a match:
+ // the public key tokens differ (signed vs. unsigned), so IsAssemblyReferenced returns false. This guards the
+ // signed-vs-unsigned branch of the public-key-token comparison.
+ public void IsAssemblyReferencedShouldReturnFalseIfPublicKeyTokenIsMissing() => _testSource.IsAssemblyReferenced(new AssemblyName("MSTest.TestFramework"), Assembly.GetExecutingAssembly().Location).Should().BeFalse();
+
+ // Same simple name but a different (non-null) public key token must also not match: the tokens are compared
+ // byte-by-byte and differ. This guards the differing-token branch of the public-key-token comparison.
+ public void IsAssemblyReferencedShouldReturnFalseIfPublicKeyTokenDiffers()
+ {
+ var assemblyName = new AssemblyName("MSTest.TestFramework");
+ assemblyName.SetPublicKeyToken([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
+ _testSource.IsAssemblyReferenced(assemblyName, Assembly.GetExecutingAssembly().Location).Should().BeFalse();
+ }
}
#endif