Skip to content

Commit

Permalink
Hadnle unmanaged executables without throwing
Browse files Browse the repository at this point in the history
  • Loading branch information
CharliePoole committed Sep 9, 2024
1 parent e3d96c1 commit 7ce369e
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 18 deletions.
10 changes: 10 additions & 0 deletions NUnitConsole.sln
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,18 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTest", "src\TestData\wpf-test\WpfTest.csproj", "{A96876EE-1A1F-4096-9B6A-5739E66B3364}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestData", "TestData", "{2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}"
ProjectSection(SolutionItems) = preProject
src\TestData\TestData.sln = src\TestData\TestData.sln
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "src\TestData\mock-assembly\mock-assembly.csproj", "{C3FF8716-052B-4D6C-81FF-E80F89AF9A80}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly-x86", "src\TestData\mock-assembly-x86\mock-assembly-x86.csproj", "{8FE8378E-5A8B-4708-8F86-35BE0DE121F7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "src\TestData\notest-assembly\notest-assembly.csproj", "{81E63A90-3191-4E99-92FF-01F9B1D3E3C5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "src\TestData\WpfApp\WpfApp.csproj", "{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -203,6 +208,10 @@ Global
{81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81E63A90-3191-4E99-92FF-01F9B1D3E3C5}.Release|Any CPU.Build.0 = Release|Any CPU
{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -234,6 +243,7 @@ Global
{C3FF8716-052B-4D6C-81FF-E80F89AF9A80} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}
{8FE8378E-5A8B-4708-8F86-35BE0DE121F7} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}
{81E63A90-3191-4E99-92FF-01F9B1D3E3C5} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}
{6B550F25-1CA5-4F3E-B631-1ECCD4CB94E4} = {2ECE1CFB-9436-4149-B7E4-1FB1786FDE9F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711}
Expand Down
3 changes: 2 additions & 1 deletion package-tests.cake
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ static ExpectedResult MockAssemblySolutionResult = new ExpectedResult("Failed")
new ExpectedAssemblyResult("mock-assembly.dll", "netcore-8.0"),
new ExpectedAssemblyResult("notest-assembly.dll", "net-4.6.2"),
new ExpectedAssemblyResult("notest-assembly.dll", "netcore-3.1"),
new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0")
new ExpectedAssemblyResult("notest-assembly.dll", "netstandard-2.0"),
new ExpectedAssemblyResult("WpfApp.exe")
}
};

Expand Down
131 changes: 131 additions & 0 deletions src/NUnitEngine/nunit.engine.core/Runners/NotRunnableTestRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Emit;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace NUnit.Engine.Runners
{
public abstract class NotRunnableTestRunner : ITestEngineRunner
{
private const string LOAD_RESULT_FORMAT =
"<test-suite type='{0}' id='{1}' name='{2}' fullname='{3}' testcasecount='0' runstate='{4}'>" +
"<properties>" +
"<property name='_SKIPREASON' value='{5}'/>" +
"</properties>" +
"</test-suite>";

private const string RUN_RESULT_FORMAT =
"<test-suite type='{0}' id='{1}' name='{2}' fullname='{3}' testcasecount='0' runstate='{4}' result='{5}' label='{6}'>" +
"<properties>" +
"<property name='_SKIPREASON' value='{7}'/>" +
"</properties>" +
"<reason>" +
"<message>{7}</message>" +
"</reason>" +
"</test-suite>";

private string _name;
private string _fullname;
private string _message;
private string _type;

protected string _runstate;
protected string _result;
protected string _label;

public NotRunnableTestRunner(string assemblyPath, string message)
{
_name = Escape(Path.GetFileName(assemblyPath));
_fullname = Escape(Path.GetFullPath(assemblyPath));
_message = Escape(message);
_type = new List<string> { ".dll", ".exe" }.Contains(Path.GetExtension(assemblyPath)) ? "Assembly" : "Unknown";
}

public string ID { get; set; }

TestEngineResult ITestEngineRunner.Load()
{
return GetLoadResult();
}

void ITestEngineRunner.Unload()
{
}

TestEngineResult ITestEngineRunner.Reload()
{
return GetLoadResult();
}

int ITestEngineRunner.CountTestCases(TestFilter filter)
{
return 0;
}

TestEngineResult ITestEngineRunner.Run(ITestEventListener listener, TestFilter filter)
{
return new TestEngineResult(string.Format(RUN_RESULT_FORMAT,
_type, TestID, _name, _fullname, _runstate, _result, _label, _message));
}

AsyncTestEngineResult ITestEngineRunner.RunAsync(ITestEventListener listener, TestFilter filter)
{
throw new NotImplementedException();
}

void ITestEngineRunner.StopRun(bool force)
{
}

TestEngineResult ITestEngineRunner.Explore(TestFilter filter)
{
return GetLoadResult();
}

void IDisposable.Dispose()
{
// Nothing to do here
}

private static string Escape(string original)
{
return original
.Replace("&", "&amp;")
.Replace("\"", "&quot;")
.Replace("'", "&apos;")
.Replace("<", "&lt;")
.Replace(">", "&gt;");
}

private TestEngineResult GetLoadResult()
{
return new TestEngineResult(string.Format(
LOAD_RESULT_FORMAT,
_type, TestID, _name, _fullname, _runstate, _message));
}

private string TestID
{
get
{
return string.IsNullOrEmpty(ID)
? "1"
: ID + "-1";
}
}
}

public class UnmanagedExecutableTestRunner : NotRunnableTestRunner
{
public UnmanagedExecutableTestRunner(string assemblyPath)
: base (assemblyPath, "Unmanaged libraries or applications are not supported")
{ }
}
}
2 changes: 1 addition & 1 deletion src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class MasterTestRunner : ITestRunner
// MasterTestRunner is the only runner that is passed back
// to users asking for an ITestRunner. The actual details of
// execution are handled by various internal runners, which
// impement ITestEngineRunner.
// implement ITestEngineRunner.
//
// Explore and execution results from MasterTestRunner are
// returned as XmlNodes, created from the internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public ITestEngineRunner MakeTestRunner(TestPackage package)
}
#else

// Unmanaged code is handled in-process irrespective of the process model
if (package.GetSetting(InternalEnginePackageSettings.ImageTargetFrameworkName, "").StartsWith("Unmanaged,"))
return new UnmanagedExecutableTestRunner(package.FullName);

ProcessModel processModel = (ProcessModel)System.Enum.Parse(
typeof(ProcessModel),
package.GetSetting(EnginePackageSettings.ProcessModel, "Default"));
Expand Down
43 changes: 27 additions & 16 deletions src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package)
targetRuntime = RuntimeType.NetCore;
targetVersion = new Version(3, 1);
break;
case "Unmanaged":
return null;
default:
throw new NUnitEngineException("Unsupported Target Framework: " + imageTargetFrameworkNameSetting);
}
Expand Down Expand Up @@ -283,26 +285,35 @@ private static void ApplyImageData(TestPackage package)
}
else if (File.Exists(packageName) && PathUtils.IsAssemblyFileType(packageName))
{
using (var assembly = AssemblyDefinition.ReadAssembly(packageName))
try
{
targetVersion = assembly.GetRuntimeVersion();
log.Debug($"Assembly {packageName} uses version {targetVersion}");

frameworkName = assembly.GetFrameworkName();
log.Debug($"Assembly {packageName} targets {frameworkName}");

if (assembly.RequiresX86())
using (var assembly = AssemblyDefinition.ReadAssembly(packageName))
{
requiresX86 = true;
log.Debug($"Assembly {packageName} will be run x86");
}

if (assembly.HasAttribute("NUnit.Framework.TestAssemblyDirectoryResolveAttribute"))
{
requiresAssemblyResolver = true;
log.Debug($"Assembly {packageName} requires default app domain assembly resolver");
targetVersion = assembly.GetRuntimeVersion();
log.Debug($"Assembly {packageName} uses version {targetVersion}");

frameworkName = assembly.GetFrameworkName();
log.Debug($"Assembly {packageName} targets {frameworkName}");

if (assembly.RequiresX86())
{
requiresX86 = true;
log.Debug($"Assembly {packageName} will be run x86");
}

if (assembly.HasAttribute("NUnit.Framework.TestAssemblyDirectoryResolveAttribute"))
{
requiresAssemblyResolver = true;
log.Debug($"Assembly {packageName} requires default app domain assembly resolver");
}
}
}
catch (BadImageFormatException)
{
// "Unmanaged" is not a valid framework identifier but we handle it upstream
// using UnmanagedCodeTestRunner, which doesn't actually try to run it.
frameworkName = "Unmanaged,Version=0.0";
}
}

if (targetVersion.Major > 0)
Expand Down
6 changes: 6 additions & 0 deletions src/TestData/TestData.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mock-assembly", "mock-assem
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "notest-assembly", "notest-assembly\notest-assembly.csproj", "{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "WpfApp\WpfApp.csproj", "{F831D879-E806-4DE7-8D80-9B94AD64744A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F412390F-E7C4-41B4-A84E-8FC4DC3FB2F7}.Release|Any CPU.Build.0 = Release|Any CPU
{F831D879-E806-4DE7-8D80-9B94AD64744A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F831D879-E806-4DE7-8D80-9B94AD64744A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F831D879-E806-4DE7-8D80-9B94AD64744A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F831D879-E806-4DE7-8D80-9B94AD64744A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
17 changes: 17 additions & 0 deletions src/TestData/WpfApp/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

namespace WpfApp
{
internal class Program
{
static void Main() { }
}
}
10 changes: 10 additions & 0 deletions src/TestData/WpfApp/WpfApp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputPath>..\..\bin\$(Configuration)\</OutputPath>
<UseWPF>true</UseWPF>
</PropertyGroup>

</Project>

0 comments on commit 7ce369e

Please sign in to comment.