Skip to content

Commit a797d09

Browse files
EvangelinkAlxandr
authored andcommitted
feat: add support for Microsoft.Testing.Platform (#152)
1 parent 308fc67 commit a797d09

16 files changed

+225
-73
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,14 @@ jobs:
3030
- name: 🔽 Setup dotnet from global.json
3131
uses: actions/setup-dotnet@v3
3232

33+
- name: 🔽 Setup just
34+
uses: extractions/setup-just@v1
35+
3336
- name: 🔍 Print dotnet info
3437
run: dotnet --info
3538

36-
- name: 🔽 Restore
37-
run: dotnet restore -p:Configuration=Release
38-
39-
- name: 🔨 Build
40-
run: dotnet build --configuration Release
41-
4239
- name: 🧪 Test
43-
run: dotnet test --no-build --configuration Release
44-
45-
- name: 📦 Pack
46-
run: dotnet pack --configuration Release --no-build
47-
if: ${{ matrix.os == 'ubuntu-latest' }}
40+
run: just test
4841

4942
- name: 🔼 Upload packages as artifact
5043
uses: actions/upload-artifact@v3

.justfile

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[private]
2+
@list:
3+
just --list
4+
5+
restore:
6+
dotnet restore -p:Configuration=Release
7+
8+
build: restore
9+
dotnet build --configuration Release --no-restore
10+
11+
pack: build
12+
dotnet pack --configuration Release --no-build
13+
14+
test-platform: pack
15+
dotnet clean ./test/Sample.Test/Sample.Test.fsproj -v:quiet
16+
dotnet test ./test/Sample.Test/Sample.Test.fsproj -p:EnableExpectoTestingPlatformIntegration=true -p:IncludeFailingTests=false
17+
18+
test-platform-failing: pack
19+
dotnet clean ./test/Sample.Test/Sample.Test.fsproj -v:quiet
20+
dotnet test ./test/Sample.Test/Sample.Test.fsproj -p:EnableExpectoTestingPlatformIntegration=true -p:IncludeFailingTests=true
21+
22+
test-legacy: pack
23+
dotnet clean ./test/Sample.Test/Sample.Test.fsproj -v:quiet
24+
dotnet test ./test/Sample.Test/Sample.Test.fsproj -p:EnableExpectoTestingPlatformIntegration=false -p:IncludeFailingTests=false
25+
26+
test-legacy-failing: pack
27+
dotnet clean ./test/Sample.Test/Sample.Test.fsproj -v:quiet
28+
dotnet test ./test/Sample.Test/Sample.Test.fsproj -p:EnableExpectoTestingPlatformIntegration=false -p:IncludeFailingTests=true
29+
30+
@check-platform:
31+
#!/usr/bin/env bash
32+
just test-platform
33+
34+
just test-platform-failing
35+
status=$?
36+
37+
if [ $status -eq 0 ]; then
38+
echo "Expected tests to fail, but they passed (status code: $Status)"
39+
exit 1
40+
fi
41+
42+
echo "Test run worked as expected"
43+
44+
@check-legacy:
45+
#!/usr/bin/env bash
46+
just test-legacy
47+
48+
just test-legacy-failing
49+
status=$?
50+
51+
if [ $status -eq 0 ]; then
52+
echo "Expected tests to fail, but they passed (status code: $Status)"
53+
exit 1
54+
fi
55+
56+
echo "Test run worked as expected"
57+
58+
@test: check-platform check-legacy

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<Project>
2-
32
<ItemGroup>
43
<PackageVersion Include="Expecto" Version="[10.0, 11.0)" />
54
<PackageVersion Include="FSharp.Core" Version="[7.0.200,)" />
65
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
6+
<PackageVersion Include="Microsoft.Testing.Extensions.VSTestBridge" Version="1.5.1" />
7+
<PackageVersion Include="Microsoft.Testing.Platform.MSBuild" Version="1.5.1" />
78
<PackageVersion Include="Microsoft.TestPlatform.ObjectModel" Version="17.12.0" />
89
<PackageVersion Include="System.Collections.Immutable" Version="[6.0.1,)" />
910
</ItemGroup>
10-
1111
</Project>

YoloDev.Expecto.TestSdk.sln

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
21
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26124.0
2+
# Visual Studio Version 17
3+
VisualStudioVersion = 17.12.35129.301
54
MinimumVisualStudioVersion = 15.0.26124.0
65
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FCE4C28E-CF2A-423F-9E7A-8A13DAACBF96}"
76
EndProject
87
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "YoloDev.Expecto.TestSdk", "src\YoloDev.Expecto.TestSdk\YoloDev.Expecto.TestSdk.fsproj", "{06D3B976-762E-4645-B70E-C87B1EDE1CEE}"
98
EndProject
10-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C5F68231-8575-44BC-B2CD-5163268E8A45}"
11-
EndProject
12-
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Sample.Test", "test\Sample.Test\Sample.Test.fsproj", "{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}"
13-
EndProject
149
Global
1510
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1611
Debug|Any CPU = Debug|Any CPU
@@ -20,9 +15,6 @@ Global
2015
Release|x64 = Release|x64
2116
Release|x86 = Release|x86
2217
EndGlobalSection
23-
GlobalSection(SolutionProperties) = preSolution
24-
HideSolutionNode = FALSE
25-
EndGlobalSection
2618
GlobalSection(ProjectConfigurationPlatforms) = postSolution
2719
{06D3B976-762E-4645-B70E-C87B1EDE1CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2820
{06D3B976-762E-4645-B70E-C87B1EDE1CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -36,21 +28,11 @@ Global
3628
{06D3B976-762E-4645-B70E-C87B1EDE1CEE}.Release|x64.Build.0 = Release|x64
3729
{06D3B976-762E-4645-B70E-C87B1EDE1CEE}.Release|x86.ActiveCfg = Release|x86
3830
{06D3B976-762E-4645-B70E-C87B1EDE1CEE}.Release|x86.Build.0 = Release|x86
39-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
41-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Debug|x64.ActiveCfg = Debug|x64
42-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Debug|x64.Build.0 = Debug|x64
43-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Debug|x86.ActiveCfg = Debug|x86
44-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Debug|x86.Build.0 = Debug|x86
45-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
46-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Release|Any CPU.Build.0 = Release|Any CPU
47-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Release|x64.ActiveCfg = Release|x64
48-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Release|x64.Build.0 = Release|x64
49-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Release|x86.ActiveCfg = Release|x86
50-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE}.Release|x86.Build.0 = Release|x86
31+
EndGlobalSection
32+
GlobalSection(SolutionProperties) = preSolution
33+
HideSolutionNode = FALSE
5134
EndGlobalSection
5235
GlobalSection(NestedProjects) = preSolution
5336
{06D3B976-762E-4645-B70E-C87B1EDE1CEE} = {FCE4C28E-CF2A-423F-9E7A-8A13DAACBF96}
54-
{2DADC6BD-672C-4B40-B0DA-0C6AD4F372BE} = {C5F68231-8575-44BC-B2CD-5163268E8A45}
5537
EndGlobalSection
5638
EndGlobal
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace YoloDev.Expecto.TestSdk
2+
3+
open System.Reflection
4+
5+
open Microsoft.Testing.Extensions.VSTestBridge.Capabilities
6+
open Microsoft.Testing.Extensions.VSTestBridge.Helpers
7+
open Microsoft.Testing.Platform.Builder
8+
open Microsoft.Testing.Platform.Capabilities.TestFramework
9+
10+
module TestApplicationBuilderExtensions =
11+
let addExpectoFramework (getTestAssemblies: unit -> Assembly seq) (builder: ITestApplicationBuilder) =
12+
let expectoExtension = ExpectoExtension()
13+
builder.AddRunSettingsService expectoExtension
14+
builder.AddTestCaseFilterService expectoExtension
15+
builder.RegisterTestFramework (
16+
(fun _ -> TestFrameworkCapabilities(VSTestBridgeExtensionBaseCapabilities())),
17+
(fun capabilities serviceProvider -> new ExpectoTestFramework(expectoExtension, getTestAssemblies, serviceProvider, capabilities))
18+
) |> ignore
19+
20+
module TestingPlatformBuilderHook =
21+
let AddExtensions(builder: ITestApplicationBuilder, arguments: string array) =
22+
TestApplicationBuilderExtensions.addExpectoFramework (fun () -> [ Assembly.GetEntryAssembly() ]) builder

src/YoloDev.Expecto.TestSdk/YoloDev.Expecto.TestSdk.fsproj

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
<Project Sdk="YoloDev.Sdk">
1+
<Project Sdk="YoloDev.Sdk">
22

33
<PropertyGroup>
44
<AssemblyName>expecto.visualstudio.dotnetcore.testadapter</AssemblyName>
55
<!-- puts build outputs in build folder in nupkg -->
66
<BuildOutputTargetFolder>build</BuildOutputTargetFolder>
77
<OutputType>Exe</OutputType>
88
<PackageId>$(MSBuildProjectName)</PackageId>
9+
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
910
</PropertyGroup>
1011

1112
<ItemGroup>
@@ -16,17 +17,21 @@
1617
<Compile Include="discovery.fs" />
1718
<Compile Include="execution.fs" />
1819
<Compile Include="adapter.fs" />
20+
<Compile Include="TestApplicationHelpers.fs" />
1921
<Compile Include="main.fs" />
2022
</ItemGroup>
2123

2224
<ItemGroup>
2325
<None Include="build/net6.0/YoloDev.Expecto.TestSdk.props" Pack="true" PackagePath="build\net6.0\" />
26+
<None Include="build/net6.0/YoloDev.Expecto.TestSdk.targets" Pack="true" PackagePath="build\net6.0\" />
2427
<None Include="build/net6.0/_._" Pack="true" PackagePath="lib\net6.0\" Visible="false" />
2528
</ItemGroup>
2629

2730
<ItemGroup>
2831
<PackageReference Include="FSharp.Core" />
2932
<PackageReference Include="Expecto" />
33+
<PackageReference Include="Microsoft.Testing.Extensions.VSTestBridge" />
34+
<PackageReference Include="Microsoft.Testing.Platform.MSBuild" />
3035
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" PrivateAssets="all" />
3136
<PackageReference Include="System.Collections.Immutable" />
3237
</ItemGroup>

src/YoloDev.Expecto.TestSdk/adapter.fs

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
namespace YoloDev.Expecto.TestSdk
22

3-
open Expecto.Impl
43
open Expecto.Tests
5-
open System.IO
64
open System.Threading
75
open System.Diagnostics
6+
open Microsoft.Testing.Extensions.VSTestBridge
7+
open Microsoft.Testing.Extensions.VSTestBridge.Requests
88
open Microsoft.VisualStudio.TestPlatform.ObjectModel
99
open Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter
10-
open Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging
10+
open System.Threading.Tasks
11+
open System.Reflection
1112

1213
[<FileExtension(".dll")>]
1314
[<FileExtension(".exe")>]
@@ -34,24 +35,17 @@ type VsTestAdapter() =
3435

3536
interface System.IDisposable with
3637
member x.Dispose() =
37-
match cts with
38-
| null -> ()
39-
| s -> s.Dispose()
38+
cts.Dispose()
4039

4140
interface ITestDiscoverer with
4241
member x.DiscoverTests(sources, discoveryContext, logger, discoverySink) =
4342
x.Breakpoint()
4443

45-
let sources = Guard.argNotNull "sources" sources
46-
let logger = Guard.argNotNull "logger" logger
47-
let discoverySink = Guard.argNotNull "discoverySink" discoverySink
48-
4944
let stopwatch = Stopwatch.StartNew()
5045
let logger = Logger(logger, stopwatch)
5146

5247
let runSettings =
53-
Option.ofObj discoveryContext
54-
|> Option.bind (fun c -> Option.ofObj c.RunSettings)
48+
Option.ofObj discoveryContext.RunSettings
5549
|> Option.map (RunSettings.read logger)
5650
|> Option.defaultValue RunSettings.defaultSettings
5751

@@ -62,7 +56,7 @@ type VsTestAdapter() =
6256
interface ITestExecutor with
6357
member x.Cancel() = cts.Cancel()
6458

65-
member x.RunTests(tests: TestCase seq, runContext: IRunContext, frameworkHandle: IFrameworkHandle) : unit =
59+
member x.RunTests(tests: TestCase seq, runContext: IRunContext | null, frameworkHandle: IFrameworkHandle | null) : unit =
6660
x.Breakpoint()
6761
let tests = Guard.argNotNull "tests" tests
6862
let runContext = Guard.argNotNull "runContext" runContext
@@ -80,7 +74,7 @@ type VsTestAdapter() =
8074
Execution.runSpecifiedTests logger runSettings frameworkHandle tests
8175
|> Async.RunSynchronously
8276

83-
member x.RunTests(sources: string seq, runContext: IRunContext, frameworkHandle: IFrameworkHandle) : unit =
77+
member x.RunTests(sources: string seq, runContext: IRunContext | null, frameworkHandle: IFrameworkHandle | null) : unit =
8478
x.Breakpoint()
8579
let sources = Guard.argNotNull "sources" sources
8680
let runContext = Guard.argNotNull "runContext" runContext
@@ -102,3 +96,37 @@ type VsTestAdapter() =
10296

10397
Execution.runTests logger runSettings frameworkHandle sources
10498
|> Async.RunSynchronously
99+
100+
/// Defines the identity of the Expecto extension for Microsoft.Testing.Platform.
101+
type ExpectoExtension() =
102+
interface Microsoft.Testing.Platform.Extensions.IExtension with
103+
member _.Uid = nameof(ExpectoExtension)
104+
member _.Version =
105+
match Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>() with
106+
| null -> "0.0.0"
107+
| x -> x.InformationalVersion
108+
member _.DisplayName = "Expecto"
109+
member _.Description = "Expecto test adapter for Microsoft Testing Platform"
110+
member _.IsEnabledAsync() = System.Threading.Tasks.Task.FromResult true
111+
112+
/// Defines the ITestFramework extension of Microsoft.Testing.Platform for Expecto using the VSTest bridge.
113+
type ExpectoTestFramework(extension, getTestAssemblies, serviceProvider, capabilities) =
114+
inherit SynchronizedSingleSessionVSTestBridgedTestFramework(extension, getTestAssemblies, serviceProvider, capabilities) with
115+
116+
let vstestAdapter = new VsTestAdapter()
117+
118+
let discoverTests (request: VSTestDiscoverTestExecutionRequest) =
119+
let discoverer = vstestAdapter :> ITestDiscoverer
120+
discoverer.DiscoverTests(request.AssemblyPaths, request.DiscoveryContext, request.MessageLogger, request.DiscoverySink)
121+
Task.CompletedTask
122+
123+
let runTests (request: VSTestRunTestExecutionRequest) (token: CancellationToken) =
124+
let runner = vstestAdapter :> ITestExecutor
125+
use _ = token.Register (fun _ -> runner.Cancel())
126+
match request.VSTestFilter.TestCases |> Option.ofNullable with
127+
| Some testCases -> runner.RunTests(testCases, request.RunContext, request.FrameworkHandle)
128+
| None -> runner.RunTests(request.AssemblyPaths, request.RunContext, request.FrameworkHandle)
129+
Task.CompletedTask
130+
131+
override _.SynchronizedDiscoverTestsAsync(request, _, _) = discoverTests request
132+
override _.SynchronizedRunTestsAsync(request, _, token) = runTests request token
Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
<Project>
2+
3+
<PropertyGroup>
4+
<!-- Makes the new platform/runner opt-in feature -->
5+
<EnableExpectoTestingPlatformIntegration Condition=" '$(EnableExpectoTestingPlatformIntegration)' == '' ">false</EnableExpectoTestingPlatformIntegration>
6+
<!-- Defines the new platform capability -->
7+
<IsTestingPlatformApplication>$(EnableExpectoTestingPlatformIntegration)</IsTestingPlatformApplication>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<!--
12+
!!! IMPORTANT !!!
13+
DO NOT CHANGE THE GUID, IT'S A WELL KNOWN EXTENSION POINT AND THIS EXTENSION NEEDS TO BE REGISTERED AT THE END
14+
WE HAVE CODE INSIDE THE TASK 'TestingPlatformEntryPoint' TO ENSURE THE ORDER OF THE REGISTRATION BASED ON THIS GUID
15+
-->
16+
<TestingPlatformBuilderHook Include="CFC308E5-E1A5-4B99-8002-17EF4D55D043">
17+
<DisplayName>Expecto</DisplayName>
18+
<TypeFullName>YoloDev.Expecto.TestSdk.TestingPlatformBuilderHook</TypeFullName>
19+
</TestingPlatformBuilderHook>
20+
</ItemGroup>
21+
222
<ItemGroup>
3-
<None Include="$(MSBuildThisFileDirectory)expecto.visualstudio.dotnetcore.testadapter.dll">
4-
<Link>expecto.visualstudio.dotnetcore.testadapter.dll</Link>
5-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
6-
<Visible>False</Visible>
7-
</None>
823
<ProjectCapability Include="TestContainer" />
924
</ItemGroup>
25+
1026
</Project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project>
2+
3+
<!-- Handle the coexistance between testing platform and Microsoft.NET.Test.Sdk -->
4+
<PropertyGroup>
5+
<GenerateTestingPlatformEntryPoint Condition=" '$(GenerateTestingPlatformEntryPoint)' == '' ">$(EnableExpectoTestingPlatformIntegration)</GenerateTestingPlatformEntryPoint>
6+
<GenerateSelfRegisteredExtensions Condition=" '$(GenerateSelfRegisteredExtensions)' == '' ">$(EnableExpectoTestingPlatformIntegration)</GenerateSelfRegisteredExtensions>
7+
<GenerateProgramFile Condition=" '$(EnableExpectoTestingPlatformIntegration)' == 'true' ">false</GenerateProgramFile>
8+
<DisableTestingPlatformServerCapability Condition=" '$(EnableExpectoTestingPlatformIntegration)' != 'true' " >true</DisableTestingPlatformServerCapability>
9+
</PropertyGroup>
10+
11+
<Choose>
12+
<!-- Avoid false warning about missing reference (msbuild bug) -->
13+
<!-- https://github.com/dotnet/msbuild/issues/9698#issuecomment-1945763467 -->
14+
<When Condition=" '$(EnableExpectoTestingPlatformIntegration)' == 'true' ">
15+
<ItemGroup>
16+
<Reference Include="expecto.visualstudio.dotnetcore.testadapter">
17+
<HintPath>$(MSBuildThisFileDirectory)expecto.visualstudio.dotnetcore.testadapter.dll</HintPath>
18+
</Reference>
19+
</ItemGroup>
20+
</When>
21+
<Otherwise>
22+
<ItemGroup>
23+
<None Include="$(MSBuildThisFileDirectory)expecto.visualstudio.dotnetcore.testadapter.dll">
24+
<Link>expecto.visualstudio.dotnetcore.testadapter.dll</Link>
25+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26+
<Visible>False</Visible>
27+
</None>
28+
</ItemGroup>
29+
</Otherwise>
30+
</Choose>
31+
32+
</Project>

src/YoloDev.Expecto.TestSdk/discovery.fs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ module internal Discovery =
6969
let internal discoverTestForSource logger source =
7070
let assembly = System.Reflection.Assembly.LoadFile source
7171

72-
if isNull assembly then failwithf "LoadFile %s returned null" source
73-
7472
getTestForAssembly logger assembly source
7573

7674
let discoverTests logger = Seq.choose (discoverTestForSource logger)

0 commit comments

Comments
 (0)