Browse Source

Implement Avalonia.Headless.NUnit integration

pull/11169/head
Max Katz 3 years ago
parent
commit
2da88e7cfc
  1. 26
      Avalonia.sln
  2. 18
      src/Headless/Avalonia.Headless.NUnit/Avalonia.Headless.NUnit.csproj
  3. 25
      src/Headless/Avalonia.Headless.NUnit/AvaloniaTest.cs
  4. 44
      src/Headless/Avalonia.Headless.NUnit/AvaloniaTestCommand.cs
  5. 24
      src/Headless/Avalonia.Headless.NUnit/AvaloniaTheory.cs
  6. 35
      src/Headless/Avalonia.Headless.NUnit/ExecutionQueue.cs

26
Avalonia.sln

@ -264,7 +264,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit", "src\Headless\Avalonia.Headless.NUnit\Avalonia.Headless.NUnit.csproj", "{ED976634-B118-43F8-8B26-0279C7A7044F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{EBA7613E-C36C-4E0C-AB45-71B143F86219}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit.UnitTests", "tests\Avalonia.Headless.NUnit.UnitTests\Avalonia.Headless.NUnit.UnitTests.csproj", "{47025FBC-2130-42EE-98C9-D3989B3B9446}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -609,10 +613,6 @@ Global
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -637,6 +637,18 @@ Global
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU
{ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.Build.0 = Release|Any CPU
{EBA7613E-C36C-4E0C-AB45-71B143F86219}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBA7613E-C36C-4E0C-AB45-71B143F86219}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA7613E-C36C-4E0C-AB45-71B143F86219}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA7613E-C36C-4E0C-AB45-71B143F86219}.Release|Any CPU.Build.0 = Release|Any CPU
{47025FBC-2130-42EE-98C9-D3989B3B9446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47025FBC-2130-42EE-98C9-D3989B3B9446}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47025FBC-2130-42EE-98C9-D3989B3B9446}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47025FBC-2130-42EE-98C9-D3989B3B9446}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -707,7 +719,6 @@ Global
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -715,6 +726,9 @@ Global
{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{ED976634-B118-43F8-8B26-0279C7A7044F} = {FF237916-7150-496B-89ED-6CA3292896E7}
{EBA7613E-C36C-4E0C-AB45-71B143F86219} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{47025FBC-2130-42EE-98C9-D3989B3B9446} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

18
src/Headless/Avalonia.Headless.NUnit/Avalonia.Headless.NUnit.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<!-- Use lower minor version, as it is supposed to be compatible -->
<PackageReference Include="NUnit" Version="3.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Headless\Avalonia.Headless.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\ApiDiff.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
</Project>

25
src/Headless/Avalonia.Headless.NUnit/AvaloniaTest.cs

@ -0,0 +1,25 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal.Commands;
namespace Avalonia.Headless.NUnit;
/// <summary>
/// Identifies a nunit test that starts on Avalonia Dispatcher
/// such that awaited expressions resume on the test's "main thread".
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class AvaloniaTestAttribute : TestCaseAttribute, IWrapSetUpTearDown
{
public TestCommand Wrap(TestCommand command)
{
var session =
HeadlessUnitTestSession.GetOrStartForAssembly(command.Test.Method?.MethodInfo.DeclaringType?.Assembly);
return new AvaloniaTestCommand(session, command);
}
}

44
src/Headless/Avalonia.Headless.NUnit/AvaloniaTestCommand.cs

@ -0,0 +1,44 @@
using System.Threading.Tasks;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using NUnit.Framework.Internal.Commands;
namespace Avalonia.Headless.NUnit;
internal class AvaloniaTestCommand : DelegatingTestCommand
{
private readonly HeadlessUnitTestSession _session;
public AvaloniaTestCommand(HeadlessUnitTestSession session, TestCommand innerCommand)
: base(innerCommand)
{
_session = session;
}
public override TestResult Execute(TestExecutionContext context)
{
return _session.Dispatcher.InvokeAsync<Task<TestResult>>(async () =>
{
var testMethod = innerCommand.Test.Method;
var methodInfo = testMethod!.MethodInfo;
var result = methodInfo.Invoke(context.TestObject, innerCommand.Test.Arguments);
// Only Task, non generic ValueTask are supported in async context. No ValueTask<> nor F# tasks.
if (result is Task task)
{
await task;
}
else if (result is ValueTask valueTask)
{
await valueTask;
}
context.CurrentResult.SetResult(ResultState.Success);
if (context.CurrentResult.AssertionResults.Count > 0)
context.CurrentResult.RecordTestCompletion();
return context.CurrentResult;
}).GetTask().Unwrap().Result;
}
}

24
src/Headless/Avalonia.Headless.NUnit/AvaloniaTheory.cs

@ -0,0 +1,24 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal.Commands;
namespace Avalonia.Headless.NUnit;
/// <summary>
/// Identifies a nunit theory that starts on Avalonia Dispatcher
/// such that awaited expressions resume on the test's "main thread".
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class AvaloniaTheoryAttribute : TheoryAttribute, IWrapSetUpTearDown
{
public TestCommand Wrap(TestCommand command)
{
var session = HeadlessUnitTestSession.GetOrStartForAssembly(command.Test.Method?.MethodInfo.DeclaringType?.Assembly);
return new AvaloniaTestCommand(session, command);
}
}

35
src/Headless/Avalonia.Headless.NUnit/ExecutionQueue.cs

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Avalonia.Headless.NUnit;
internal static class ExecutionQueue
{
static bool _running;
static Queue<Func<Task>> _queue = new();
static async void TryExecuteNext()
{
if (_running || _queue.Count == 0) return;
try
{
_running = true;
await _queue.Dequeue()();
}
finally
{
_running = false;
}
TryExecuteNext();
}
static void ExecuteOnQueue(this Dispatcher dispatcher, Func<Task> cb)
{
dispatcher.Post(() =>
{
_queue.Enqueue(cb);
TryExecuteNext();
});
}
}
Loading…
Cancel
Save