Browse Source
Fix random test failures and add empty dispatcher verification to tests (#17628)
* Add VerifyEmptyDispatcherAfterTestAttribute
* Use VerifyEmptyDispatcherAfterTest and fix failing tests
* Remove unsupported timeout from sync xUnit tests
pull/17657/head
Julien Lebosquain
1 year ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with
141 additions and
44 deletions
-
Avalonia.sln.DotSettings
-
build/XUnit.props
-
src/Avalonia.Base/Threading/Dispatcher.Queue.cs
-
src/Avalonia.Base/Threading/DispatcherOperation.cs
-
src/Avalonia.Base/Threading/DispatcherPriorityQueue.cs
-
tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs
-
tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
-
tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
-
tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_EffectiveViewportChanged.cs
-
tests/Avalonia.Base.UnitTests/Layout/LayoutableTests_LayoutRounding.cs
-
tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs
-
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
-
tests/Avalonia.Controls.UnitTests/Properties/AssemblyInfo.cs
-
tests/Avalonia.Controls.UnitTests/TabControlTests.cs
-
tests/Avalonia.Headless.UnitTests/InputTests.cs
-
tests/Avalonia.Headless.UnitTests/RenderingTests.cs
-
tests/Avalonia.Headless.UnitTests/ServicesTests.cs
-
tests/Avalonia.Headless.UnitTests/ThreadingTests.cs
-
tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
-
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
-
tests/Avalonia.ReactiveUI.UnitTests/Attributes.cs
-
tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
-
tests/Avalonia.UnitTests/InvariantCultureAttribute.cs
-
tests/Avalonia.UnitTests/VerifyEmptyDispatcherAfterTestAttribute.cs
|
|
|
@ -22,6 +22,7 @@ |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=TYPEDEF/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=UNION_005FMEMBER/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String> |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String> |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></s:String> |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /></s:String> |
|
|
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Interfaces/@EntryIndexedValue"><Policy Inspect="False" Prefix="I" Suffix="" Style="AaBb" /></s:String> |
|
|
|
|
|
|
|
@ -1,14 +1,14 @@ |
|
|
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|
|
|
<ItemGroup> |
|
|
|
<PackageReference Include="xunit" Version="2.4.2" /> |
|
|
|
<PackageReference Include="xunit.assert" Version="2.4.2" /> |
|
|
|
<PackageReference Include="xunit.core" Version="2.4.2" /> |
|
|
|
<PackageReference Include="xunit.extensibility.core" Version="2.4.2" /> |
|
|
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.2" /> |
|
|
|
<PackageReference Include="xunit.runner.console" Version="2.4.2" /> |
|
|
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" Condition="'$(TargetFramework)' != 'netstandard2.0'" /> |
|
|
|
<PackageReference Include="xunit" Version="2.9.2" /> |
|
|
|
<PackageReference Include="xunit.assert" Version="2.9.2" /> |
|
|
|
<PackageReference Include="xunit.core" Version="2.9.2" /> |
|
|
|
<PackageReference Include="xunit.extensibility.core" Version="2.9.2" /> |
|
|
|
<PackageReference Include="xunit.extensibility.execution" Version="2.9.2" /> |
|
|
|
<PackageReference Include="xunit.runner.console" Version="2.9.2" /> |
|
|
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" Condition="'$(TargetFramework)' != 'netstandard2.0'" /> |
|
|
|
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> |
|
|
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" /> |
|
|
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> |
|
|
|
</ItemGroup> |
|
|
|
<PropertyGroup> |
|
|
|
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\avalonia.snk</AssemblyOriginatorKeyFile> |
|
|
|
|
|
|
|
@ -1,4 +1,5 @@ |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Diagnostics; |
|
|
|
using System.Threading; |
|
|
|
|
|
|
|
@ -270,4 +271,25 @@ public partial class Dispatcher |
|
|
|
lock (InstanceLock) |
|
|
|
return _queue.MaxPriority >= priority; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets all pending jobs, unordered, without removing them.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>Only use between unit tests!</remarks>
|
|
|
|
/// <returns>A list of jobs.</returns>
|
|
|
|
internal List<DispatcherOperation> GetJobs() |
|
|
|
{ |
|
|
|
lock (InstanceLock) |
|
|
|
return _queue.PeekAll(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Clears all pending jobs.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>Only use between unit tests!</remarks>
|
|
|
|
internal void ClearJobs() |
|
|
|
{ |
|
|
|
lock (InstanceLock) |
|
|
|
_queue.Clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1,12 +1,13 @@ |
|
|
|
using System; |
|
|
|
using System.ComponentModel; |
|
|
|
using System.Diagnostics; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using System.Runtime.ExceptionServices; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
|
|
|
|
namespace Avalonia.Threading; |
|
|
|
|
|
|
|
[DebuggerDisplay("{DebugDisplay}")] |
|
|
|
public class DispatcherOperation |
|
|
|
{ |
|
|
|
protected readonly bool ThrowOnUiThread; |
|
|
|
@ -25,7 +26,7 @@ public class DispatcherOperation |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected object? Callback; |
|
|
|
protected internal object? Callback; |
|
|
|
protected object? TaskSource; |
|
|
|
|
|
|
|
internal DispatcherOperation? SequentialPrev { get; set; } |
|
|
|
@ -53,6 +54,16 @@ public class DispatcherOperation |
|
|
|
Dispatcher = dispatcher; |
|
|
|
} |
|
|
|
|
|
|
|
internal string DebugDisplay |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
var method = (Callback as Delegate)?.Method; |
|
|
|
var methodDisplay = method is null ? "???" : method.DeclaringType + "." + method.Name; |
|
|
|
return $"{methodDisplay} [{Priority}]"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// An event that is raised when the operation is aborted or canceled.
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
@ -398,6 +398,23 @@ internal class DispatcherPriorityQueue |
|
|
|
// Step 3: cleanup
|
|
|
|
item.SequentialPrev = item.SequentialNext = null; |
|
|
|
} |
|
|
|
|
|
|
|
public List<DispatcherOperation> PeekAll() |
|
|
|
{ |
|
|
|
var operations = new List<DispatcherOperation>(); |
|
|
|
|
|
|
|
for (var item = _head; item is not null; item = item.SequentialNext) |
|
|
|
operations.Add(item); |
|
|
|
|
|
|
|
return operations; |
|
|
|
} |
|
|
|
|
|
|
|
public void Clear() |
|
|
|
{ |
|
|
|
_priorityChains.Clear(); |
|
|
|
_cacheReusableChains.Clear(); |
|
|
|
_head = _tail = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -415,4 +432,4 @@ internal class PriorityChain |
|
|
|
public DispatcherOperation? Head { get; set; } |
|
|
|
|
|
|
|
public DispatcherOperation? Tail { get; set; } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -15,7 +15,7 @@ using Xunit.Sdk; |
|
|
|
|
|
|
|
namespace Avalonia.Base.UnitTests.Composition; |
|
|
|
|
|
|
|
public class CompositionAnimationTests |
|
|
|
public class CompositionAnimationTests : ScopedTestBase |
|
|
|
{ |
|
|
|
|
|
|
|
class AnimationDataProvider : DataAttribute |
|
|
|
@ -114,4 +114,4 @@ public class CompositionAnimationTests |
|
|
|
return Name; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -7,7 +7,7 @@ using Xunit; |
|
|
|
|
|
|
|
namespace Avalonia.Base.UnitTests.Input |
|
|
|
{ |
|
|
|
public class AccessKeyHandlerTests |
|
|
|
public class AccessKeyHandlerTests : ScopedTestBase |
|
|
|
{ |
|
|
|
[Fact] |
|
|
|
public void Should_Raise_Key_Events_For_Unregistered_Access_Key() |
|
|
|
|
|
|
|
@ -14,7 +14,7 @@ using Moq; |
|
|
|
|
|
|
|
namespace Avalonia.Base.UnitTests.Input; |
|
|
|
|
|
|
|
public abstract class PointerTestsBase |
|
|
|
public abstract class PointerTestsBase : ScopedTestBase |
|
|
|
{ |
|
|
|
private protected static void SetHit(Mock<IHitTester> renderer, Control? hit) |
|
|
|
{ |
|
|
|
|
|
|
|
@ -11,7 +11,7 @@ using Xunit; |
|
|
|
|
|
|
|
namespace Avalonia.Base.UnitTests.Layout |
|
|
|
{ |
|
|
|
public class LayoutableTests_EffectiveViewportChanged |
|
|
|
public class LayoutableTests_EffectiveViewportChanged : ScopedTestBase |
|
|
|
{ |
|
|
|
[Fact] |
|
|
|
public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree_And_Layout_Pass_Has_Not_Run() |
|
|
|
@ -38,9 +38,7 @@ namespace Avalonia.Base.UnitTests.Layout |
|
|
|
[Fact] |
|
|
|
public async Task EffectiveViewportChanged_Raised_When_Control_Added_To_Tree_And_Layout_Pass_Has_Run() |
|
|
|
{ |
|
|
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
await RunOnUIThread.Execute(async () => |
|
|
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
{ |
|
|
|
var root = CreateRoot(); |
|
|
|
var target = new Canvas(); |
|
|
|
@ -64,9 +62,7 @@ namespace Avalonia.Base.UnitTests.Layout |
|
|
|
[Fact] |
|
|
|
public async Task EffectiveViewportChanged_Raised_When_Root_LayedOut_And_Then_Control_Added_To_Tree_And_Layout_Pass_Runs() |
|
|
|
{ |
|
|
|
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
await RunOnUIThread.Execute(async () => |
|
|
|
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
|
|
|
{ |
|
|
|
var root = CreateRoot(); |
|
|
|
var target = new Canvas(); |
|
|
|
|
|
|
|
@ -6,7 +6,7 @@ using Xunit.Sdk; |
|
|
|
|
|
|
|
namespace Avalonia.Base.UnitTests.Layout |
|
|
|
{ |
|
|
|
public class LayoutableTests_LayoutRounding |
|
|
|
public class LayoutableTests_LayoutRounding : ScopedTestBase |
|
|
|
{ |
|
|
|
[Theory] |
|
|
|
[InlineData(100, 100)] |
|
|
|
@ -112,7 +112,7 @@ namespace Avalonia.Base.UnitTests.Layout |
|
|
|
{ |
|
|
|
if (!expected.NearlyEquals(actual)) |
|
|
|
{ |
|
|
|
throw new EqualException(expected, actual); |
|
|
|
throw EqualException.ForMismatchedValues(expected, actual); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -120,7 +120,7 @@ namespace Avalonia.Base.UnitTests.Layout |
|
|
|
{ |
|
|
|
if (!expected.NearlyEquals(actual)) |
|
|
|
{ |
|
|
|
throw new EqualException(expected, actual); |
|
|
|
throw EqualException.ForMismatchedValues(expected, actual); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1,7 +1,9 @@ |
|
|
|
using System.Reflection; |
|
|
|
using Avalonia.UnitTests; |
|
|
|
using Xunit; |
|
|
|
|
|
|
|
[assembly: AssemblyTitle("Avalonia.UnitTests")] |
|
|
|
[assembly: AssemblyTitle("Avalonia.Base.UnitTests")] |
|
|
|
|
|
|
|
// Don't run tests in parallel.
|
|
|
|
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
|
|
|
[assembly: VerifyEmptyDispatcherAfterTest] |
|
|
|
|
|
|
|
@ -358,8 +358,10 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void FlowDirection_Of_RectangleContent_Shuold_Be_LeftToRight() |
|
|
|
public void FlowDirection_Of_RectangleContent_Should_Be_LeftToRight() |
|
|
|
{ |
|
|
|
using var app = UnitTestApplication.Start(TestServices.StyledWindow); |
|
|
|
|
|
|
|
var target = new ComboBox |
|
|
|
{ |
|
|
|
FlowDirection = FlowDirection.RightToLeft, |
|
|
|
@ -385,6 +387,8 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
[Fact] |
|
|
|
public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform() |
|
|
|
{ |
|
|
|
using var app = UnitTestApplication.Start(TestServices.StyledWindow); |
|
|
|
|
|
|
|
var parentContent = new Decorator() |
|
|
|
{ |
|
|
|
Child = new Control() |
|
|
|
|
|
|
|
@ -1,7 +1,9 @@ |
|
|
|
using System.Reflection; |
|
|
|
using Avalonia.UnitTests; |
|
|
|
using Xunit; |
|
|
|
|
|
|
|
[assembly: AssemblyTitle("Avalonia.Controls.UnitTests")] |
|
|
|
|
|
|
|
// Don't run tests in parallel.
|
|
|
|
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
|
|
|
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
|
|
|
[assembly: VerifyEmptyDispatcherAfterTest] |
|
|
|
|
|
|
|
@ -408,6 +408,8 @@ namespace Avalonia.Controls.UnitTests |
|
|
|
[Fact] |
|
|
|
public void Previous_ContentTemplate_Is_Not_Reused_When_TabItem_Changes() |
|
|
|
{ |
|
|
|
using var app = UnitTestApplication.Start(TestServices.StyledWindow); |
|
|
|
|
|
|
|
int templatesBuilt = 0; |
|
|
|
|
|
|
|
var target = new TabControl |
|
|
|
|
|
|
|
@ -1,6 +1,7 @@ |
|
|
|
using System; |
|
|
|
using System.Reactive.Disposables; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using Avalonia.Controls; |
|
|
|
using Avalonia.Input; |
|
|
|
using Avalonia.Layout; |
|
|
|
@ -35,12 +36,11 @@ public class InputTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Should_Click_Button_On_Window() |
|
|
|
{ |
|
|
|
Assert.True(_setupApp == Application.Current); |
|
|
|
|
|
|
|
var buttonClicked = false; |
|
|
|
var button = new Button |
|
|
|
{ |
|
|
|
@ -62,7 +62,7 @@ public class InputTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Change_Window_Position() |
|
|
|
{ |
|
|
|
|
|
|
|
@ -14,7 +14,7 @@ public class RenderingTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Should_Render_Last_Frame_To_Bitmap() |
|
|
|
{ |
|
|
|
@ -43,7 +43,7 @@ public class RenderingTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Should_Not_Crash_On_GeometryGroup() |
|
|
|
{ |
|
|
|
@ -79,7 +79,7 @@ public class RenderingTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Should_Not_Crash_On_CombinedGeometry() |
|
|
|
{ |
|
|
|
@ -110,7 +110,7 @@ public class RenderingTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Should_Not_Hang_With_Non_Trivial_Layout() |
|
|
|
{ |
|
|
|
|
|
|
|
@ -13,7 +13,7 @@ public class ServicesTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Can_Access_Screens() |
|
|
|
{ |
|
|
|
|
|
|
|
@ -12,7 +12,7 @@ public class ThreadingTests |
|
|
|
#if NUNIT
|
|
|
|
[AvaloniaTest, Timeout(10000)] |
|
|
|
#elif XUNIT
|
|
|
|
[AvaloniaFact(Timeout = 10000)] |
|
|
|
[AvaloniaFact] |
|
|
|
#endif
|
|
|
|
public void Should_Be_On_Dispatcher_Thread() |
|
|
|
{ |
|
|
|
|
|
|
|
@ -411,16 +411,16 @@ namespace Avalonia.IntegrationTests.Appium |
|
|
|
// the position of a centered window can be off by a bit. From initial testing, looks
|
|
|
|
// like this shouldn't be more than 10 pixels.
|
|
|
|
if (Math.Abs(expected.X - actual.X) > 10) |
|
|
|
throw new EqualException(expected, actual); |
|
|
|
throw EqualException.ForMismatchedValues(expected, actual); |
|
|
|
if (Math.Abs(expected.Y - actual.Y) > 10) |
|
|
|
throw new EqualException(expected, actual); |
|
|
|
throw EqualException.ForMismatchedValues(expected, actual); |
|
|
|
} |
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|
|
|
{ |
|
|
|
if (Math.Abs(expected.X - actual.X) > 15) |
|
|
|
throw new EqualException(expected, actual); |
|
|
|
throw EqualException.ForMismatchedValues(expected, actual); |
|
|
|
if (Math.Abs(expected.Y - actual.Y) > 15) |
|
|
|
throw new EqualException(expected, actual); |
|
|
|
throw EqualException.ForMismatchedValues(expected, actual); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
|
|
|
|
@ -37,11 +37,11 @@ |
|
|
|
</Compile> |
|
|
|
</ItemGroup> |
|
|
|
<ItemGroup> |
|
|
|
<PackageReference Update="xunit.runner.console" Version="2.7.0"> |
|
|
|
<PackageReference Update="xunit.runner.console" Version="2.9.2"> |
|
|
|
<PrivateAssets>all</PrivateAssets> |
|
|
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|
|
|
</PackageReference> |
|
|
|
<PackageReference Update="xunit.runner.visualstudio" Version="2.5.7"> |
|
|
|
<PackageReference Update="xunit.runner.visualstudio" Version="2.8.2"> |
|
|
|
<PrivateAssets>all</PrivateAssets> |
|
|
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|
|
|
</PackageReference> |
|
|
|
|
|
|
|
@ -1,6 +1,8 @@ |
|
|
|
using Avalonia.UnitTests; |
|
|
|
using Xunit; |
|
|
|
|
|
|
|
// Required to avoid InvalidOperationException sometimes thrown
|
|
|
|
// from Splat.MemoizingMRUCache.cs which is not thread-safe.
|
|
|
|
// Thrown when trying to access WhenActivated concurrently.
|
|
|
|
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
|
|
|
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
|
|
|
[assembly: VerifyEmptyDispatcherAfterTest] |
|
|
|
|
|
|
|
@ -8,7 +8,7 @@ using Xunit; |
|
|
|
|
|
|
|
namespace Avalonia.ReactiveUI.UnitTests |
|
|
|
{ |
|
|
|
public class ReactiveUserControlTest |
|
|
|
public class ReactiveUserControlTest : ScopedTestBase |
|
|
|
{ |
|
|
|
public class ExampleViewModel : ReactiveObject, IActivatableViewModel |
|
|
|
{ |
|
|
|
|
|
|
|
@ -15,7 +15,7 @@ namespace Avalonia.UnitTests; |
|
|
|
/// Some tests are formatting numbers, expecting a dot as a decimal point.
|
|
|
|
/// Use this fixture to set the current culture to the invariant culture.
|
|
|
|
/// </remarks>
|
|
|
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] |
|
|
|
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] |
|
|
|
public sealed class InvariantCultureAttribute : BeforeAfterTestAttribute |
|
|
|
{ |
|
|
|
private CultureInfo? _previousCulture; |
|
|
|
|
|
|
|
@ -0,0 +1,38 @@ |
|
|
|
using System; |
|
|
|
using System.Linq; |
|
|
|
using System.Reflection; |
|
|
|
using Avalonia.Controls; |
|
|
|
using Avalonia.Threading; |
|
|
|
using Xunit; |
|
|
|
using Xunit.Sdk; |
|
|
|
|
|
|
|
namespace Avalonia.UnitTests; |
|
|
|
|
|
|
|
public sealed class VerifyEmptyDispatcherAfterTestAttribute : BeforeAfterTestAttribute |
|
|
|
{ |
|
|
|
public override void After(MethodInfo methodUnderTest) |
|
|
|
{ |
|
|
|
if (typeof(ScopedTestBase).IsAssignableFrom(methodUnderTest.DeclaringType)) |
|
|
|
return; |
|
|
|
|
|
|
|
var dispatcher = Dispatcher.UIThread; |
|
|
|
var jobs = dispatcher.GetJobs(); |
|
|
|
if (jobs.Count == 0) |
|
|
|
return; |
|
|
|
|
|
|
|
dispatcher.ClearJobs(); |
|
|
|
|
|
|
|
// Ignore the Control.Loaded callback. It might happen synchronously or might be posted.
|
|
|
|
if (jobs.Count == 1 && IsLoadedCallback(jobs[0])) |
|
|
|
return; |
|
|
|
|
|
|
|
Assert.Fail( |
|
|
|
$"The test left {jobs.Count} unprocessed dispatcher {(jobs.Count == 1 ? "job" : "jobs")}:\n" + |
|
|
|
$"{string.Join(Environment.NewLine, jobs.Select(job => $" - {job.DebugDisplay}"))}\n" + |
|
|
|
$"Consider using ScopedTestBase or UnitTestApplication.Start()."); |
|
|
|
|
|
|
|
static bool IsLoadedCallback(DispatcherOperation job) |
|
|
|
=> job.Priority == DispatcherPriority.Loaded && |
|
|
|
(job.Callback as Delegate)?.Method.DeclaringType?.DeclaringType == typeof(Control); |
|
|
|
} |
|
|
|
} |