From 9ff2b0b817dbb81b8b7febc2d2a792fb5fe791b8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 4 Sep 2025 17:29:43 -0700 Subject: [PATCH 1/2] Dispose font manager after headless session --- .../Avalonia.Headless/HeadlessUnitTestSession.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs index 466f1f90443..796d0d860e2 100644 --- a/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs +++ b/src/Headless/Avalonia.Headless/HeadlessUnitTestSession.cs @@ -5,6 +5,8 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; using Avalonia.Metadata; using Avalonia.Reactive; using Avalonia.Threading; @@ -124,9 +126,10 @@ internal Task DispatchCore(Func> action, bool ca private IDisposable EnsureApplication() { var scope = AvaloniaLocator.EnterScope(); + var oldContext = SynchronizationContext.Current; try { - Dispatcher.ResetForUnitTests(); + Dispatcher.ResetBeforeUnitTests(); _appBuilder.SetupUnsafe(); } catch @@ -137,9 +140,12 @@ private IDisposable EnsureApplication() return Disposable.Create(() => { - scope.Dispose(); + ((ToolTipService?)AvaloniaLocator.Current.GetService())?.Dispose(); + (AvaloniaLocator.Current.GetService() as IDisposable)?.Dispose(); Dispatcher.ResetForUnitTests(); - SynchronizationContext.SetSynchronizationContext(null); + scope.Dispose(); + Dispatcher.ResetBeforeUnitTests(); + SynchronizationContext.SetSynchronizationContext(oldContext); }); } From d0300ed993a6a3fb27e538797270cfb663de3637 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 4 Sep 2025 17:52:05 -0700 Subject: [PATCH 2/2] Add xunit/nunit LeakTests --- .../Avalonia.Headless.UnitTests/LeakTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/Avalonia.Headless.UnitTests/LeakTests.cs diff --git a/tests/Avalonia.Headless.UnitTests/LeakTests.cs b/tests/Avalonia.Headless.UnitTests/LeakTests.cs new file mode 100644 index 00000000000..b8e2f9ca53c --- /dev/null +++ b/tests/Avalonia.Headless.UnitTests/LeakTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Media; + +namespace Avalonia.Headless.UnitTests; + +public class LeakTests +{ + public static IEnumerable TestData { get; } = + Enumerable.Range(0, 50).Select(i => new object[] { i.ToString() }); + + private static WeakReference s_previousFontManager; + +#if NUNIT + [TestCaseSource(nameof(TestData))] + [AvaloniaTest, Timeout(10000)] +#elif XUNIT + [MemberData(nameof(TestData))] + [AvaloniaTheory] +#endif + public void Previous_FontManager_Should_Be_Collected(string data) + { + // Arrange + var fontManager = new WeakReference(FontManager.Current); + var button = new Button { Content = data }; + var window = new Window + { + Content = button + }; + + // Act, just some interaction, to make sure the FontManager is actually used + window.Show(); + button.Focus(); + window.MouseDown(new Point(1,1), MouseButton.Left); + window.Close(); + + // Assert + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + if (s_previousFontManager is not null) + { + Assert.False(s_previousFontManager.IsAlive); + } + + s_previousFontManager = fontManager; + } +}