Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5b07073
Abstract VSTest message logging in PlatformServices (Phase 0/1)
Jul 1, 2026
40cabbb
Abstract VSTest result reporting in PlatformServices (Phase 5)
Evangelink Jul 1, 2026
f05621d
Address review comments on Phase 1 logging PR
Jul 2, 2026
3f85af0
Clarify ITestResultRecorder remarks re: platform boundary
Evangelink Jul 2, 2026
7fd1fcc
Merge remote-tracking branch 'origin/dev/amauryleve/abstract-vstest-r…
Jul 2, 2026
f1e1f36
Abstract VSTest test filtering in PlatformServices (Phase 2 of platfo…
Evangelink Jul 3, 2026
f846008
Abstract VSTest discovery sink in PlatformServices (Phase 3 of platfo…
Evangelink Jul 3, 2026
4f02b6e
Abstract VSTest execution input in PlatformServices (Phase 4 of platf…
Evangelink Jul 3, 2026
98ec16e
Neutralize deployment input in PlatformServices (Phase 6a of platform…
Evangelink Jul 3, 2026
371124a
Neutralize test result recording in PlatformServices (Phase 6b) (#9579)
Evangelink Jul 3, 2026
274e479
Neutralize test message logging in PlatformServices execution engine …
Evangelink Jul 3, 2026
1a37ffa
Neutralize run-settings input in PlatformServices host layer (Phase 6c2)
Jul 3, 2026
dc4c4d8
Move test-case filter parsing to adapter boundary (Phase 6d-1)
Jul 3, 2026
7caa937
Remove VSTest run/discovery context from PlatformServices (Phase 6d-2)
Jul 5, 2026
668335a
Remove IFrameworkHandle from the PlatformServices execution engine (P…
Jul 5, 2026
1e61c13
Relocate VSTest logger/sink bridges to the adapter (Phase 6e-2)
Evangelink Jul 5, 2026
ab2cc59
Neutralize the trait type on UnitTestElement (Phase 6e-3a)
Evangelink Jul 5, 2026
1ffa5fa
Relocate the UnitTestElement<->TestCase conversion to the adapter (Ph…
Evangelink Jul 5, 2026
fbcafef
Neutralize runsettings parsing helpers (Phase 6e-4a)
Evangelink Jul 5, 2026
24e471c
Resolve the object-model assembly by name in AppDomain wiring (Phase …
Evangelink Jul 5, 2026
8261580
Resolve source-host anchor assemblies by name (Phase 6e-4c1)
Evangelink Jul 5, 2026
e6dc77c
Reimplement the source assembly-reference check without ObjectModel (…
Evangelink Jul 5, 2026
9197a3f
Vendor a neutral SuspendCodeCoverage in PlatformServices (Phase 6e-4c3)
Evangelink Jul 5, 2026
16552c6
Drop the ObjectModel package reference from PlatformServices (Phase 7)
Evangelink Jul 5, 2026
ce6569b
Handle null/empty public key tokens in ArePublicKeyTokensEqual
Evangelink Jul 5, 2026
b2a5341
Merge remote-tracking branch 'origin/main' into dev/amauryleve/vstest…
Evangelink Jul 7, 2026
b3a8b1e
Resolve post-merge semantic conflicts after merging main
Evangelink Jul 7, 2026
8191388
Add DesktopTestSourceTests coverage for public key token mismatch
Evangelink Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@
that reference is suppressed above, so they are referenced explicitly here. -->
<PackageReference Include="System.Threading.Tasks.Extensions" Condition=" '$(TargetFramework)' == '$(NetFrameworkMinimum)' OR '$(TargetFramework)' == '$(UwpMinimum)' " />
<PackageReference Include="System.Memory" Condition=" '$(TargetFramework)' == '$(NetFrameworkMinimum)' OR '$(TargetFramework)' == '$(UwpMinimum)' " />

<!-- The adapter's VSTest-facing code (VSTestAdapter\*, AdapterTestProperties) uses the VSTest object model
directly. On other TFMs it flows in transitively via Microsoft.Testing.Extensions.VSTestBridge, but that
reference is not added for UWP (see above), and PlatformServices no longer references the object model,
so it is referenced explicitly here for UWP. -->
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Condition=" '$(TargetFramework)' == '$(UwpMinimum)' " />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Apartment state parsed from the <c>ExecutionThreadApartmentState</c> run settings and the
/// <c>mstest:execution:executionApartmentState</c> configuration value. This is the adapter's own neutral
/// replacement for the former VSTest <c>PlatformAbstractions.PlatformApartmentState</c> enum.
/// </summary>
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,
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,20 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" />
<PackageReference Include="System.Threading.Tasks.Extensions" Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == '$(NetFrameworkMinimum)' OR '$(TargetFramework)' == '$(UwpMinimum)' " />
<!-- Span/MemoryMarshal used by the linked XxHash code below live in System.Memory on these TFMs.
Referenced explicitly so the compiled assembly binds the System.Memory assembly version 4.0.5.0
(see SystemMemoryVersion in Directory.Packages.props) instead of the transitive 4.5.5 (4.0.1.2). -->
<PackageReference Include="System.Memory" Condition=" '$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == '$(NetFrameworkMinimum)' OR '$(TargetFramework)' == '$(UwpMinimum)' " />
</ItemGroup>

<!-- System.Configuration (ConfigurationManager / ConfigurationElementCollection used by TestDataSource on .NET
Framework) was previously pulled in transitively via Microsoft.TestPlatform.ObjectModel. It is a framework
assembly, so it is referenced directly now that the object-model package reference is removed. -->
<ItemGroup Condition=" '$(TargetFramework)' == '$(NetFrameworkMinimum)' ">
<Reference Include="System.Configuration" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(RepoRoot)src/Polyfills/**/*.cs" Link="Polyfills\%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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."),
};
}

Expand Down Expand Up @@ -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")),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Comment thread
Evangelink marked this conversation as resolved.

if (left.Length != right.Length)
{
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<PackageReference Include="Moq" />
<PackageReference Include="AwesomeAssertions" />
<PackageReference Include="StrongNamer" />
<!-- This test uses ObjectModel's XmlRunSettingsUtilities directly; it was previously provided transitively
through the MSTestAdapter.PlatformServices project reference, which no longer depends on the object model. -->
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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<string> 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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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