From 9bf7abcdfefbeda3a129b24c8925cc0123cf7c72 Mon Sep 17 00:00:00 2001 From: Ilya Pospelov Date: Tue, 30 Sep 2025 16:45:05 +0300 Subject: [PATCH] Run Avalonia.LeakTests on CI (#19720) * Check Avalonia.LeakTests * Enable DotMemoryUnit * Fix TransitionTests --- nukebuild/Build.cs | 50 +++++++++++++++++---- tests/Avalonia.LeakTests/TransitionTests.cs | 7 +++ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 4f577444d1..b0d8106d32 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Xml.Linq; @@ -10,8 +12,10 @@ using Nuke.Common; using Nuke.Common.Tooling; using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.Npm; +using Nuke.Common.Utilities; using static Nuke.Common.EnvironmentInfo; using static Nuke.Common.IO.PathConstruction; +using static Nuke.Common.Tools.DotMemoryUnit.DotMemoryUnitTasks; using static Nuke.Common.Tools.DotNet.DotNetTasks; using static Serilog.Log; using MicroCom.CodeGenerator; @@ -183,6 +187,31 @@ partial class Build : NukeBuild }); void RunCoreTest(string projectName) + { + RunCoreTest(projectName, (project, tfm) => + { + DotNetTest(c => ApplySetting(c, project,tfm)); + }); + } + + void RunCoreDotMemoryUnit(string projectName) + { + RunCoreTest(projectName, (project, tfm) => + { + var testSettings = ApplySetting(new DotNetTestSettings(), project, tfm); + var testToolPath = GetToolPathInternal(new DotNetTasks(), testSettings); + var testArgs = GetArguments(testSettings).JoinSpace(); + DotMemoryUnit($"{testToolPath} --propagate-exit-code -- {testArgs:nq}"); + }); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = nameof(GetToolPathInternal))] + extern static string GetToolPathInternal(ToolTasks tasks, ToolOptions options); + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = nameof(GetArguments))] + extern static IEnumerable GetArguments(ToolOptions options); + } + + void RunCoreTest(string projectName, Action runTest) { Information($"Running tests from {projectName}"); var project = RootDirectory.GlobFiles(@$"**\{projectName}.csproj").FirstOrDefault() @@ -228,17 +257,20 @@ partial class Build : NukeBuild Information($"Running for {projectName} ({tfm}) ..."); - DotNetTest(c => ApplySetting(c) - .SetProjectFile(project) - .SetFramework(tfm) - .EnableNoBuild() - .EnableNoRestore() - .When(_ => Parameters.PublishTestResults, _ => _ - .SetLoggers("trx") - .SetResultsDirectory(Parameters.TestResultsRoot))); + runTest(project, tfm); } } + DotNetTestSettings ApplySetting(DotNetTestSettings settings, string project, string tfm) => + ApplySetting(settings) + .SetProjectFile(project) + .SetFramework(tfm) + .EnableNoBuild() + .EnableNoRestore() + .When(_ => Parameters.PublishTestResults, _ => _ + .SetLoggers("trx") + .SetResultsDirectory(Parameters.TestResultsRoot)); + Target RunHtmlPreviewerTests => _ => _ .DependsOn(CompileHtmlPreviewer) .OnlyWhenStatic(() => !(Parameters.SkipPreviewer || Parameters.SkipTests)) @@ -296,7 +328,7 @@ partial class Build : NukeBuild { void DoMemoryTest() { - RunCoreTest("Avalonia.LeakTests"); + RunCoreDotMemoryUnit("Avalonia.LeakTests"); } ControlFlow.ExecuteWithRetry(DoMemoryTest, delay: TimeSpan.FromMilliseconds(3)); }); diff --git a/tests/Avalonia.LeakTests/TransitionTests.cs b/tests/Avalonia.LeakTests/TransitionTests.cs index 2e68acf526..aedb42506e 100644 --- a/tests/Avalonia.LeakTests/TransitionTests.cs +++ b/tests/Avalonia.LeakTests/TransitionTests.cs @@ -3,6 +3,7 @@ using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Styling; +using Avalonia.Threading; using Avalonia.UnitTests; using JetBrains.dotMemoryUnit; using Xunit; @@ -99,6 +100,9 @@ namespace Avalonia.LeakTests var result = run(); + // Process all Loaded events to free control reference(s) + Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded); + dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is