diff --git a/Avalonia.sln b/Avalonia.sln
index b7625ed1dd..dda16bb5ad 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -90,7 +90,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\ImageSharp.props = build\ImageSharp.props
- build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props
diff --git a/build/JetBrains.dotMemoryUnit.props b/build/JetBrains.dotMemoryUnit.props
deleted file mode 100644
index 5d74d474cf..0000000000
--- a/build/JetBrains.dotMemoryUnit.props
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index d3c2cb1d01..c24d2f0c32 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -15,7 +15,6 @@ 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;
@@ -194,23 +193,6 @@ partial class Build : NukeBuild
});
}
- 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}");
@@ -325,11 +307,7 @@ partial class Build : NukeBuild
.DependsOn(Compile)
.Executes(() =>
{
- void DoMemoryTest()
- {
- RunCoreDotMemoryUnit("Avalonia.LeakTests");
- }
- ControlFlow.ExecuteWithRetry(DoMemoryTest, delay: TimeSpan.FromMilliseconds(3));
+ RunCoreTest("Avalonia.LeakTests");
});
Target ZipFiles => _ => _
@@ -418,8 +396,8 @@ partial class Build : NukeBuild
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)
.DependsOn(RunToolsTests)
- .DependsOn(RunHtmlPreviewerTests);
- //.DependsOn(RunLeakTests); // dotMemory Unit doesn't support modern .NET versions, see https://youtrack.jetbrains.com/issue/DMU-300/
+ .DependsOn(RunHtmlPreviewerTests)
+ .DependsOn(RunLeakTests);
Target Package => _ => _
.DependsOn(RunTests)
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index 7a0828dc94..b57414b32e 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -11,7 +11,6 @@
true
-
diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs
index 30c1f78c36..8bcb61ff99 100644
--- a/src/Avalonia.Base/Animation/Animatable.cs
+++ b/src/Avalonia.Base/Animation/Animatable.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Data;
+using Avalonia.PropertyStore;
#nullable enable
@@ -266,6 +267,21 @@ namespace Avalonia.Animation
return value;
}
+ ///
+ /// For unit tests only!
+ ///
+ internal TransitionInstance? TryGetTransitionInstance(Transition transition)
+ {
+ if (_transitionState is not null && _transitionState.TryGetValue(transition, out var transitionState))
+ {
+ var valueEntry = transitionState.Instance as BindingEntryBase;
+ var transitionObservable = valueEntry?.Source as TransitionObservableBase;
+ return transitionObservable?.Progress as TransitionInstance;
+ }
+
+ return null;
+ }
+
private class TransitionState
{
public IDisposable? Instance { get; set; }
diff --git a/src/Avalonia.Base/Animation/TransitionObservableBase.cs b/src/Avalonia.Base/Animation/TransitionObservableBase.cs
index 92462dc537..819a8a9338 100644
--- a/src/Avalonia.Base/Animation/TransitionObservableBase.cs
+++ b/src/Avalonia.Base/Animation/TransitionObservableBase.cs
@@ -22,6 +22,11 @@ namespace Avalonia.Animation
_easing = easing;
}
+ ///
+ /// For unit tests only!
+ ///
+ internal IObservable Progress => _progress;
+
///
/// Produces value at given progress time point.
///
diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
index bbcab307ac..19047b1673 100644
--- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
+++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
@@ -51,7 +51,7 @@ namespace Avalonia.PropertyStore
public AvaloniaProperty Property { get; }
AvaloniaProperty IValueEntry.Property => Property;
protected ValueFrame Frame { get; }
- protected object Source { get; }
+ protected internal object Source { get; }
public void Dispose()
{
diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
index 660e264a8b..f1c22e11e1 100644
--- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
+++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj
@@ -2,9 +2,7 @@
$(AvsCurrentTargetFramework)
-
-
diff --git a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
index da33bc6c79..ecaca2db07 100644
--- a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
+++ b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
@@ -1,7 +1,6 @@
#nullable enable
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Subjects;
using System.Runtime.CompilerServices;
@@ -12,20 +11,12 @@ using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Avalonia.Threading;
using Avalonia.UnitTests;
-using JetBrains.dotMemoryUnit;
using Xunit;
-using Xunit.Abstractions;
namespace Avalonia.LeakTests
{
- [DotMemoryUnit(FailIfRunWithoutSupport = false)]
public class AvaloniaObjectTests : ScopedTestBase
{
- public AvaloniaObjectTests(ITestOutputHelper atr)
- {
- DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
- }
-
[Fact]
public void Binding_To_Direct_Property_Does_Not_Get_Collected()
{
diff --git a/tests/Avalonia.LeakTests/BindingExpressionTests.cs b/tests/Avalonia.LeakTests/BindingExpressionTests.cs
index 4b38cfe384..5a6eddc869 100644
--- a/tests/Avalonia.LeakTests/BindingExpressionTests.cs
+++ b/tests/Avalonia.LeakTests/BindingExpressionTests.cs
@@ -2,91 +2,94 @@
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Data.Core;
-using Avalonia.Markup.Data;
using Avalonia.UnitTests;
-using JetBrains.dotMemoryUnit;
using Xunit;
-using Xunit.Abstractions;
namespace Avalonia.LeakTests
{
- [DotMemoryUnit(FailIfRunWithoutSupport = false)]
- public class BindingExpressionTests
+ public class BindingExpressionTests : ScopedTestBase
{
- public BindingExpressionTests(ITestOutputHelper atr)
- {
- DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
- }
-
[Fact]
public void Should_Not_Keep_Source_Alive_ObservableCollection()
{
- Func run = () =>
+ static WeakReference CreateExpression()
{
- var source = new { Foo = new AvaloniaList { "foo", "bar" } };
+ var list = new AvaloniaList { "foo", "bar" };
+ var source = new { Foo = list };
var target = BindingExpression.Create(source, o => o.Foo);
target.ToObservable().Subscribe(_ => { });
- return target;
- };
+ return new WeakReference(list);
+ }
+
+ var weakSource = CreateExpression();
+ Assert.True(weakSource.IsAlive);
- var result = run();
+ GC.Collect();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is>()).ObjectsCount));
+ Assert.False(weakSource.IsAlive);
}
[Fact]
public void Should_Not_Keep_Source_Alive_ObservableCollection_With_DataValidation()
{
- Func run = () =>
+ static WeakReference CreateExpression()
{
- var source = new { Foo = new AvaloniaList { "foo", "bar" } };
+ var list = new AvaloniaList { "foo", "bar" };
+ var source = new { Foo = list };
var target = BindingExpression.Create(source, o => o.Foo, enableDataValidation: true);
target.ToObservable().Subscribe(_ => { });
- return target;
- };
+ return new WeakReference(list);
+ }
- var result = run();
+ var weakSource = CreateExpression();
+ Assert.True(weakSource.IsAlive);
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is>()).ObjectsCount));
+ GC.Collect();
+
+ Assert.False(weakSource.IsAlive);
}
[Fact]
public void Should_Not_Keep_Source_Alive_NonIntegerIndexer()
{
- Func run = () =>
+ static WeakReference CreateExpression()
{
- var source = new { Foo = new NonIntegerIndexer() };
+ var indexer = new NonIntegerIndexer();
+ var source = new { Foo = indexer };
var target = BindingExpression.Create(source, o => o.Foo);
target.ToObservable().Subscribe(_ => { });
- return target;
- };
+ return new WeakReference(indexer);
+ }
+
+ var weakSource = CreateExpression();
+ Assert.True(weakSource.IsAlive);
- var result = run();
+ GC.Collect();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount));
+ Assert.False(weakSource.IsAlive);
}
[Fact]
public void Should_Not_Keep_Source_Alive_MethodBinding()
{
- Func run = () =>
+ static WeakReference CreateExpression()
{
- var source = new { Foo = new MethodBound() };
+ var methodBound = new MethodBound();
+ var source = new { Foo = methodBound };
var target = BindingExpression.Create(source, o => (Action)o.Foo.A);
target.ToObservable().Subscribe(_ => { });
- return target;
- };
+ return new WeakReference(methodBound);
+ }
+
+ var weakSource = CreateExpression();
+ Assert.True(weakSource.IsAlive);
- var result = run();
+ GC.Collect();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount));
+ Assert.False(weakSource.IsAlive);
}
private class MethodBound
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 5da96511ce..47d8417bd6 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -1,95 +1,80 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
-using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Input;
-using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
-using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
-using JetBrains.dotMemoryUnit;
using Moq;
using Xunit;
-using Xunit.Abstractions;
namespace Avalonia.LeakTests
{
- [DotMemoryUnit(FailIfRunWithoutSupport = false)]
- public class ControlTests
+ public class ControlTests : ScopedTestBase
{
- // Need to have the collection as field, so GC will not free it
- private readonly ObservableCollection _observableCollection = new();
-
- public ControlTests(ITestOutputHelper atr)
- {
- DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
- }
-
- [Fact]
+ [ReleaseFact]
public void Canvas_Is_Freed()
{
using (Start())
{
- Func run = () =>
+ static WeakReference Run()
{
+ var canvas = new Canvas();
var window = new Window
{
- Content = new Canvas()
+ Content = canvas
};
window.Show();
// Do a layout and make sure that Canvas gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType