Skip to content

Commit

Permalink
Initial support of Microsoft.Testing.Platform (#1153)
Browse files Browse the repository at this point in the history
* Initial support of Microsoft.Testing.Platform

* Bump bridge version

* Drop telemetry

* Enable trace writer fix for NUnit

* Bump version of the bridge

* Comment out object model v11

* Remove samples and "local" entry in nuget.config

* Tests: drop Microsoft.TestPlatform.ObjectModel v11

* Fix ObjectModel v11 test

* Fix broken tests

The copy of the file was moved from the .props to the .targets

* Bump platform version in nuspec

* Nit: tab vs space
  • Loading branch information
Evangelink authored May 3, 2024
1 parent a042f08 commit 72e9b6b
Show file tree
Hide file tree
Showing 16 changed files with 293 additions and 32 deletions.
2 changes: 2 additions & 0 deletions NUnit3TestAdapter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net462", "net462", "{D41249E3-C080-4B66-8CBD-99CE4D309A10}"
ProjectSection(SolutionItems) = preProject
nuget\net462\NUnit3TestAdapter.props = nuget\net462\NUnit3TestAdapter.props
nuget\net462\NUnit3TestAdapter.targets = nuget\net462\NUnit3TestAdapter.targets
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit.TestAdapter.Tests.Acceptance", "src\NUnit.TestAdapter.Tests.Acceptance\NUnit.TestAdapter.Tests.Acceptance.csproj", "{3FAC7EE0-664F-4B11-918B-8E0FF865EE4C}"
Expand All @@ -59,6 +60,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netcoreapp3.1", "netcoreapp3.1", "{2F940513-5B8F-45A5-A188-7C5D03D1B50D}"
ProjectSection(SolutionItems) = preProject
nuget\netcoreapp3.1\NUnit3TestAdapter.props = nuget\netcoreapp3.1\NUnit3TestAdapter.props
nuget\netcoreapp3.1\NUnit3TestAdapter.targets = nuget\netcoreapp3.1\NUnit3TestAdapter.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{3D1159FD-FC4D-4750-87EA-F477B3912B3C}"
Expand Down
2 changes: 2 additions & 0 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ Task("CreateWorkingImage")
CreateDirectory(net462Dir);
CopyFiles(net462Files, net462Dir);
CopyFileToDirectory("nuget/net462/NUnit3TestAdapter.props", net462Dir);
CopyFileToDirectory("nuget/net462/NUnit3TestAdapter.targets", net462Dir);
var netcoreDir = PACKAGE_IMAGE_DIR + "build/" + NETCOREAPP_TFM;
DotNetCorePublish(ADAPTER_PROJECT, new DotNetCorePublishSettings
Expand All @@ -234,6 +235,7 @@ Task("CreateWorkingImage")
Framework = NETCOREAPP_TFM
});
CopyFileToDirectory($"nuget/{NETCOREAPP_TFM}/NUnit3TestAdapter.props", netcoreDir);
CopyFileToDirectory($"nuget/{NETCOREAPP_TFM}/NUnit3TestAdapter.targets", netcoreDir);
});

Task("PackageZip")
Expand Down
1 change: 0 additions & 1 deletion nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
<packageSources>
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<!--<add key="Local" value="c:\nuget" />-->
</packageSources>
</configuration>
13 changes: 13 additions & 0 deletions nuget/NUnit3TestAdapter.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@
<tags>test visualstudio testadapter nunit nunit3 dotnet</tags>

<developmentDependency>false</developmentDependency>

<dependencies>
<group targetFramework="net462">
<dependency id="Microsoft.Testing.Extensions.VSTestBridge" version="1.1.0" />
<dependency id="Microsoft.Testing.Platform.MSBuild" version="1.1.0" />
</group>
<group targetFramework="netcoreapp3.1">
<dependency id="Microsoft.Testing.Extensions.VSTestBridge" version="1.1.0" />
<dependency id="Microsoft.Testing.Platform.MSBuild" version="1.1.0" />
</group>
</dependencies>

This comment has been minimized.

Copy link
@nbalu

nbalu Aug 1, 2024

There's a problem here. The hooks which will be introduced with the new framework will be added but currently the nuspec do not add direct reference to the NUnit3.TestAdapter. Thus any inherited projects will face a build error that the TestPlatformEntryPoint.cs is missing a reference to:
NUnit.VisualStudio.TestAdapter.TestingPlatformAdapter.TestingPlatformBuilderHook.AddExtensions(builder, args);

The .target file contains a condition which is tied to '$(EnableNUnitRunner)' == 'true' but in my opinion it should be tied to GenerateTestingPlatformEntryPoint as the platorm will rely on that. If the package is added and the setting is set we need that reference.

</metadata>
<files>
<file src="..\..\images\nunit_256.png" target="" />
Expand All @@ -36,6 +47,7 @@
<file src="build\net462\nunit.engine.core.dll" target="build\net462\nunit.engine.core.dll" />
<file src="build\net462\testcentric.engine.metadata.dll" target="build\net462\testcentric.engine.metadata.dll"/>
<file src="build\net462\NUnit3TestAdapter.props" target="build\net462\NUnit3TestAdapter.props" />
<file src="build\net462\NUnit3TestAdapter.targets" target="build\net462\NUnit3TestAdapter.targets" />

<file src="build\netcoreapp3.1\NUnit3.TestAdapter.dll" target="build\netcoreapp3.1\NUnit3.TestAdapter.dll" />
<file src="build\netcoreapp3.1\NUnit3.TestAdapter.pdb" target="build\netcoreapp3.1\NUnit3.TestAdapter.pdb" />
Expand All @@ -44,6 +56,7 @@
<file src="build\netcoreapp3.1\nunit.engine.core.dll" target="build\netcoreapp3.1\nunit.engine.core.dll" />
<file src="build\netcoreapp3.1\testcentric.engine.metadata.dll" target="build\netcoreapp3.1\testcentric.engine.metadata.dll"/>
<file src="build\netcoreapp3.1\NUnit3TestAdapter.props" target="build\netcoreapp3.1\NUnit3TestAdapter.props" />
<file src="build\netcoreapp3.1\NUnit3TestAdapter.targets" target="build\netcoreapp3.1\NUnit3TestAdapter.targets" />


</files>
Expand Down
23 changes: 18 additions & 5 deletions nuget/net462/NUnit3TestAdapter.props
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<EnableNUnitRunner Condition=" '$(EnableNUnitRunner)' == '' ">false</EnableNUnitRunner>
<IsTestingPlatformApplication>$(EnableNUnitRunner)</IsTestingPlatformApplication>
</PropertyGroup>

<ItemGroup>
<!--
!!! IMPORTANT !!!
DO NOT CHANGE THE GUID, IT'S A WELL KNOWN EXTENSION POINT AND THIS EXTENSION NEEDS TO BE REGISTERED AT THE END
WE HAVE CODE INSIDE THE TASK 'TestingPlatformEntryPoint' TO ENSURE THE ORDER OF THE REGISTRATION BASED ON THIS GUID
-->
<TestingPlatformBuilderHook Include="2E8E7F63-61DB-4EDB-A21E-5BF48279A7B8" Condition=" $(GenerateTestingPlatformEntryPoint) == 'true' " >
<DisplayName>NUnit</DisplayName>
<TypeFullName>NUnit.VisualStudio.TestAdapter.TestingPlatformAdapter.TestingPlatformBuilderHook</TypeFullName>
</TestingPlatformBuilderHook>
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll">
<Link>NUnit3.TestAdapter.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</None>
<None Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.pdb" Condition="Exists('$(MSBuildThisFileDirectory)NUnit3.TestAdapter.pdb')">
<Link>NUnit3.TestAdapter.pdb</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
31 changes: 31 additions & 0 deletions nuget/net462/NUnit3TestAdapter.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- Handle the coexistance between Microsoft.Testing.Platform and Microsoft.NET.Test.Sdk -->
<PropertyGroup>
<GenerateTestingPlatformEntryPoint Condition=" '$(GenerateTestingPlatformEntryPoint)' == '' ">$(EnableNUnitRunner)</GenerateTestingPlatformEntryPoint>
<GenerateProgramFile Condition=" '$(EnableNUnitRunner)' == 'true' ">false</GenerateProgramFile>
</PropertyGroup>

<Choose>
<!-- Avoid false warning about missing reference (msbuild bug) -->
<!-- https://github.com/dotnet/msbuild/issues/9698#issuecomment-1945763467 -->
<When Condition=" '$(EnableNUnitRunner)' == 'true' ">
<ItemGroup>
<Reference Include="NUnit3.TestAdapter">
<HintPath>$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll</HintPath>
</Reference>
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll">
<Link>NUnit3.TestAdapter.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</None>
</ItemGroup>
</Otherwise>
</Choose>

</Project>
23 changes: 18 additions & 5 deletions nuget/netcoreapp3.1/NUnit3TestAdapter.props
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<EnableNUnitRunner Condition=" '$(EnableNUnitRunner)' == '' ">false</EnableNUnitRunner>
<IsTestingPlatformApplication>$(EnableNUnitRunner)</IsTestingPlatformApplication>
</PropertyGroup>

<ItemGroup>
<!--
!!! IMPORTANT !!!
DO NOT CHANGE THE GUID, IT'S A WELL KNOWN EXTENSION POINT AND THIS EXTENSION NEEDS TO BE REGISTERED AT THE END
WE HAVE CODE INSIDE THE TASK 'TestingPlatformEntryPoint' TO ENSURE THE ORDER OF THE REGISTRATION BASED ON THIS GUID
-->
<TestingPlatformBuilderHook Include="2E8E7F63-61DB-4EDB-A21E-5BF48279A7B8" Condition=" $(GenerateTestingPlatformEntryPoint) == 'true' " >
<DisplayName>NUnit</DisplayName>
<TypeFullName>NUnit.VisualStudio.TestAdapter.TestingPlatformAdapter.TestingPlatformBuilderHook</TypeFullName>
</TestingPlatformBuilderHook>
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll">
<Link>NUnit3.TestAdapter.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</None>
<None Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.pdb" Condition="Exists('$(MSBuildThisFileDirectory)NUnit3.TestAdapter.pdb')">
<Link>NUnit3.TestAdapter.pdb</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
31 changes: 31 additions & 0 deletions nuget/netcoreapp3.1/NUnit3TestAdapter.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- Handle the coexistance between Microsoft.Testing.Platform and Microsoft.NET.Test.Sdk -->
<PropertyGroup>
<GenerateTestingPlatformEntryPoint Condition=" '$(GenerateTestingPlatformEntryPoint)' == '' ">$(EnableNUnitRunner)</GenerateTestingPlatformEntryPoint>
<GenerateProgramFile Condition=" '$(EnableNUnitRunner)' == 'true' ">false</GenerateProgramFile>
</PropertyGroup>

<Choose>
<!-- Avoid false warning about missing reference (msbuild bug) -->
<!-- https://github.com/dotnet/msbuild/issues/9698#issuecomment-1945763467 -->
<When Condition=" '$(EnableNUnitRunner)' == 'true' ">
<ItemGroup>
<Reference Include="NUnit3.TestAdapter">
<HintPath>$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll</HintPath>
</Reference>
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll">
<Link>NUnit3.TestAdapter.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</None>
</ItemGroup>
</Otherwise>
</Choose>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ public void Legacy_csproj_with_packages_config()
</Target>
<Import Project='packages\Microsoft.CodeCoverage.15.9.0\build\netstandard1.0\Microsoft.CodeCoverage.targets' Condition=""Exists('packages\Microsoft.CodeCoverage.15.9.0\build\netstandard1.0\Microsoft.CodeCoverage.targets')"" />
<Import Project='packages\Microsoft.NET.Test.Sdk.15.9.0\build\net45\Microsoft.Net.Test.Sdk.targets' Condition=""Exists('packages\Microsoft.NET.Test.Sdk.15.9.0\build\net45\Microsoft.Net.Test.Sdk.targets')"" />
<Import Project='packages\NUnit3TestAdapter.{NuGetPackageVersion}\build\{LowestNetfxTarget}\NUnit3TestAdapter.targets' Condition=""Exists('packages\NUnit3TestAdapter.{NuGetPackageVersion}\build\{LowestNetfxTarget}\NUnit3TestAdapter.targets')"" />
</Project>");

AddPackagesConfig(workspace);
Expand Down Expand Up @@ -527,6 +528,7 @@ public void Legacy_vbproj_with_packages_config()
</Target>
<Import Project='packages\Microsoft.CodeCoverage.15.9.0\build\netstandard1.0\Microsoft.CodeCoverage.targets' Condition=""Exists('packages\Microsoft.CodeCoverage.15.9.0\build\netstandard1.0\Microsoft.CodeCoverage.targets')"" />
<Import Project='packages\Microsoft.NET.Test.Sdk.15.9.0\build\net45\Microsoft.Net.Test.Sdk.targets' Condition=""Exists('packages\Microsoft.NET.Test.Sdk.15.9.0\build\net45\Microsoft.Net.Test.Sdk.targets')"" />
<Import Project='packages\NUnit3TestAdapter.{NuGetPackageVersion}\build\{LowestNetfxTarget}\NUnit3TestAdapter.targets' Condition=""Exists('packages\NUnit3TestAdapter.{NuGetPackageVersion}\build\{LowestNetfxTarget}\NUnit3TestAdapter.targets')"" />
</Project>");

AddPackagesConfig(workspace);
Expand Down
10 changes: 2 additions & 8 deletions src/NUnitTestAdapter/NUnit.TestAdapter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Testing.Extensions.VSTestBridge" Version="1.1.0" />
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />
<PackageReference Include="nunit" Version="3.14.0" />
<PackageReference Include="nunit.engine" Version="3.17.0" />
<PackageReference Include="TestCentric.Metadata" Version="3.0.2" Aliases="TestCentric" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="11.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="15.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<Folder Include="TestFilterConverter\" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Testing.Extensions.VSTestBridge;
using Microsoft.Testing.Extensions.VSTestBridge.Requests;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.Messages;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;

using NUnit.Framework.Internal;

namespace NUnit.VisualStudio.TestAdapter.TestingPlatformAdapter
{
internal sealed class NUnitBridgedTestFramework : SynchronizedSingleSessionVSTestBridgedTestFramework
{
private static readonly object InitializationLock = new();
private static bool initialized;

public NUnitBridgedTestFramework(NUnitExtension extension, Func<IEnumerable<Assembly>> getTestAssemblies,
IServiceProvider serviceProvider, ITestFrameworkCapabilities capabilities)
: base(extension, getTestAssemblies, serviceProvider, capabilities)
{
PatchNUnit3InternalLoggerBug();
}

/// <inheritdoc />
protected override Task SynchronizedDiscoverTestsAsync(VSTestDiscoverTestExecutionRequest request, IMessageBus messageBus,
CancellationToken cancellationToken)
{
new NUnit3TestDiscoverer()
.DiscoverTests(request.AssemblyPaths, request.DiscoveryContext, request.MessageLogger, request.DiscoverySink);

return Task.CompletedTask;
}

/// <inheritdoc />
protected override Task SynchronizedRunTestsAsync(VSTestRunTestExecutionRequest request, IMessageBus messageBus,
CancellationToken cancellationToken)
{
ITestExecutor executor = new NUnit3TestExecutor();
using (cancellationToken.Register(executor.Cancel))
{
if (request.VSTestFilter.TestCases is { } testCases)
{
executor.RunTests(testCases, request.RunContext, request.FrameworkHandle);
}
else
{
executor.RunTests(request.AssemblyPaths, request.RunContext, request.FrameworkHandle);
}
}

return Task.CompletedTask;
}

private static void PatchNUnit3InternalLoggerBug()
{
// NUnit3 will internally call InternalTracer.Initialize(...), and it will check that it is not called multiple times.
// We call it multiple times in server mode, in indeterminate order (it does not always have to be Discover and then Run),
// When InternalTracer.Initialize is called with Off, it will not set traceWriter internal field to anything, but when you call it again
// it will try to write "Initialize was already called" via this writer without null check and will fail with NRE.
//
// Patch for this was implemented in NUnit4, but won't be backported to NUnit3 because we are hitting a bit of an edge case:
// https://github.com/nunit/nunit/issues/4255
//
// To fix us, we set the writer to a null writer, so any subsequent calls to Initialize will write to null instead of failing.
// We also need to do this under a lock, and not rely on the InternalTracer.Initialized, because that might be set to late and we would
// re-enter the method twice.
if (initialized)
{
return;
}

lock (InitializationLock)
{
if (initialized)
{
return;
}

var nopWriter = new InternalTraceWriter(new StreamWriter(Stream.Null));

// Initialize log level to be Off (see issue https://github.com/microsoft/testanywhere/issues/1369)
// because we don't have settings from the request available yet, and the internal tracer is static, so it would set to the
// level that is set by the first request and always keep it that way.
//
// Alternatively we could hook this up to a ILogger, and write the logs via a TextWriter.
InternalTrace.Initialize(nopWriter, InternalTraceLevel.Off);

// When we allow the trace level to be set then we need to set the internal writer field only when the level is Off.
// In that case you will need to do something like this:
// FieldInfo traceLevelField = typeof(InternalTrace).GetField("traceLevel", BindingFlags.Static | BindingFlags.NonPublic)!;
// bool isOff = ((InternalTraceLevel)traceLevelField.GetValue(null)!) != InternalTraceLevel.Off;
// if (isOff)
// {
// FieldInfo traceWriterField = typeof(InternalTrace).GetField("traceWriter", BindingFlags.Static | BindingFlags.NonPublic)!;
// traceWriterField.SetValue(null, nopWriter);
// }

FieldInfo traceWriterField = typeof(InternalTrace).GetField("traceWriter", BindingFlags.Static | BindingFlags.NonPublic)!;
traceWriterField.SetValue(null, nopWriter);

initialized = true;
}
}
}
}
20 changes: 20 additions & 0 deletions src/NUnitTestAdapter/TestingPlatformAdapter/NUnitExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading.Tasks;

using Microsoft.Testing.Platform.Extensions;

namespace NUnit.VisualStudio.TestAdapter.TestingPlatformAdapter
{
internal sealed class NUnitExtension : IExtension
{
public string Uid => nameof(NUnitExtension);

public string DisplayName => "NUnit";

// TODO: Decide whether to read from assembly or use hardcoded string.
public string Version => "4.5.0";

public string Description => "NUnit adapter for Microsoft Testing Platform";

public Task<bool> IsEnabledAsync() => Task.FromResult(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Reflection;

using Microsoft.Testing.Extensions.VSTestBridge.Capabilities;
using Microsoft.Testing.Extensions.VSTestBridge.Helpers;
using Microsoft.Testing.Platform.Builder;
using Microsoft.Testing.Platform.Capabilities.TestFramework;

namespace NUnit.VisualStudio.TestAdapter.TestingPlatformAdapter
{
public static class TestApplicationBuilderExtensions
{
public static void AddNUnit(this ITestApplicationBuilder testApplicationBuilder, Func<IEnumerable<Assembly>> getTestAssemblies)
{
NUnitExtension extension = new();
testApplicationBuilder.AddRunSettingsService(extension);
testApplicationBuilder.AddTestCaseFilterService(extension);
testApplicationBuilder.RegisterTestFramework(
_ => new TestFrameworkCapabilities(new VSTestBridgeExtensionBaseCapabilities()),
(capabilities, serviceProvider) => new NUnitBridgedTestFramework(extension, getTestAssemblies, serviceProvider, capabilities));
}
}
}
Loading

0 comments on commit 72e9b6b

Please sign in to comment.