Skip to content

Commit 4597e35

Browse files
EvangelinkCopilot
andauthored
feat: emit public TestMethodIdentifierProperty and stop advertising vstestProvider (#284)
BREAKING-CHANGE: Depending on your version of .NET, this may require setting `test.runner` to `Microsoft.Testing.Platform` in `global.json`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 81a245f commit 4597e35

3 files changed

Lines changed: 49 additions & 4 deletions

File tree

src/YoloDev.Expecto.TestSdk/TestApplicationHelpers.fs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,37 @@ namespace YoloDev.Expecto.TestSdk
22

33
open System.Reflection
44

5-
open Microsoft.Testing.Extensions.VSTestBridge.Capabilities
5+
open Microsoft.Testing.Extensions.TrxReport.Abstractions
66
open Microsoft.Testing.Extensions.VSTestBridge.Helpers
77
open Microsoft.Testing.Platform.Builder
88
open Microsoft.Testing.Platform.Capabilities.TestFramework
99

10+
// We intentionally do NOT use VSTestBridgeExtensionBaseCapabilities because that type also implements
11+
// INamedFeatureCapability and advertises the "vstestProvider" capability. When a server advertises
12+
// vstestProvider, Visual Studio Test Explorer consumes the legacy "vstest.TestCase.*" properties
13+
// (serialized through Microsoft.Testing.Platform's internal SerializableKeyValuePairStringProperty
14+
// key/value bag) instead of the public, structured location.* properties.
15+
//
16+
// ExpectoTestFramework.AddAdditionalProperties now emits the public TestMethodIdentifierProperty
17+
// (serialized as location.type / location.method), so by not advertising vstestProvider we let the IDE
18+
// consume the public properties and stop depending on the internal key/value-pair shape.
19+
//
20+
// This mirrors MSTest's MSTestCapabilities:
21+
// https://github.com/microsoft/testfx/blob/5c6ea3bf01f1247736fbbbba0ffdd8a8b38840dc/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/TestApplicationBuilderExtensions.cs
22+
// MSTest can implement the internal IInternalVSTestBridgeTrxReportCapability (it is on the bridge's
23+
// InternalsVisibleTo list); external adapters cannot, so we implement the public ITrxReportCapability.
24+
type internal ExpectoTestFrameworkCapabilities() =
25+
interface ITrxReportCapability with
26+
member _.IsSupported = true
27+
member _.Enable() = ()
28+
1029
module TestApplicationBuilderExtensions =
1130
let addExpectoFramework (getTestAssemblies: unit -> Assembly seq) (builder: ITestApplicationBuilder) =
1231
let expectoExtension = ExpectoExtension()
1332
builder.AddRunSettingsService expectoExtension
1433
builder.AddTestCaseFilterService expectoExtension
1534
builder.RegisterTestFramework (
16-
(fun _ -> TestFrameworkCapabilities(VSTestBridgeExtensionBaseCapabilities())),
35+
(fun _ -> TestFrameworkCapabilities(ExpectoTestFrameworkCapabilities())),
1736
(fun capabilities serviceProvider -> new ExpectoTestFramework(expectoExtension, getTestAssemblies, serviceProvider, capabilities))
1837
) |> ignore
1938

src/YoloDev.Expecto.TestSdk/adapter.fs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ open System.Threading
55
open System.Diagnostics
66
open Microsoft.Testing.Extensions.VSTestBridge
77
open Microsoft.Testing.Extensions.VSTestBridge.Requests
8+
open Microsoft.Testing.Platform.Extensions.Messages
89
open Microsoft.VisualStudio.TestPlatform.ObjectModel
910
open Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter
1011
open System.Threading.Tasks
@@ -128,3 +129,23 @@ type ExpectoTestFramework(extension, getTestAssemblies, serviceProvider, capabil
128129

129130
override _.SynchronizedDiscoverTestsAsync(request, _, _) = discoverTests request
130131
override _.SynchronizedRunTestsAsync(request, _, token) = runTests request token
132+
133+
// Expecto tests are values in nested test lists, not CLR methods, so there is no real managed
134+
// type/method. We synthesize a public TestMethodIdentifierProperty from the (dot-joined)
135+
// FullyQualifiedName so Visual Studio can recover namespace/class/method via location.type /
136+
// location.method without the legacy vstest.TestCase.* key/value-pair properties.
137+
// NOTE: this assumes the default `--join-with .` separator; per-row identity relies on TestNode.Uid.
138+
override _.AddAdditionalProperties(testNode: TestNode, testCase: TestCase) =
139+
let fqn = testCase.FullyQualifiedName
140+
if not (System.String.IsNullOrEmpty fqn) then
141+
let lastDot = fqn.LastIndexOf '.'
142+
let typeName, methodName =
143+
if lastDot > 0 then fqn.Substring(0, lastDot), fqn.Substring(lastDot + 1)
144+
else "", fqn
145+
let assemblyFullName =
146+
try AssemblyName.GetAssemblyName(testCase.Source).FullName
147+
with _ -> ""
148+
let property =
149+
TestMethodIdentifierProperty(
150+
assemblyFullName, "", typeName, methodName, 0, Array.empty<string>, "System.Void")
151+
testNode.Properties.Add property

src/YoloDev.Expecto.TestSdk/execution.fs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,13 @@ module private PrinterAdapter =
8080
result.Outcome <- TestOutcome.Skipped
8181
result.EndTime <- DateTimeOffset.Now
8282

83-
result.Messages.Add
84-
<| TestResultMessage(TestResultMessage.AdditionalInfoCategory, sprintf "Skipped: %s" reason)
83+
// Surface the skip reason via ErrorMessage rather than a TestResultMessage with
84+
// AdditionalInfoCategory. The Microsoft.Testing.Platform VSTest bridge only knows how to
85+
// convert StandardError/StandardOut/DebugTrace message categories when building the TRX
86+
// report and throws UnreachableException on any other category, so an AdditionalInfo message
87+
// crashes the MTP run. For skipped tests the bridge maps ErrorMessage to
88+
// SkippedTestNodeStateProperty, and the legacy VSTest runner also shows it as the skip reason.
89+
result.ErrorMessage <- sprintf "Skipped: %s" reason
8590

8691
recordEnd result
8792

0 commit comments

Comments
 (0)