| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Remoting.Contexts;
- using Avalonia.Controls;
- using Avalonia.Controls.Shapes;
- using Avalonia.Controls.Templates;
- using Avalonia.Diagnostics;
- using Avalonia.Input;
- using Avalonia.Layout;
- using Avalonia.Media;
- using Avalonia.Platform;
- using Avalonia.Rendering;
- using Avalonia.Styling;
- 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 ControlTests(ITestOutputHelper atr)
- {
- DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
- }
- [Fact]
- public void Canvas_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var window = new Window
- {
- Content = new Canvas()
- };
- window.Show();
- // Do a layout and make sure that Canvas gets added to visual tree.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<Canvas>(window.Presenter.Child);
- // Clear the content and ensure the Canvas is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
- }
- }
- [Fact]
- public void Named_Canvas_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var scope = new NameScope();
- var window = new Window
- {
- Content = new Canvas
- {
- Name = "foo"
- }.RegisterInNameScope(scope)
- };
- NameScope.SetNameScope(window, scope);
- window.Show();
- // Do a layout and make sure that Canvas gets added to visual tree.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
- Assert.IsType<Canvas>(window.Presenter.Child);
- // Clear the content and ensure the Canvas is removed.
- window.Content = null;
- NameScope.SetNameScope(window, null);
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
- }
- }
- [Fact]
- public void ScrollViewer_With_Content_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var window = new Window
- {
- Content = new ScrollViewer
- {
- Content = new Canvas()
- }
- };
- window.Show();
- // Do a layout and make sure that ScrollViewer gets added to visual tree and its
- // template applied.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<ScrollViewer>(window.Presenter.Child);
- Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
- // Clear the content and ensure the ScrollViewer is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
- }
- }
- [Fact]
- public void TextBox_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var window = new Window
- {
- Content = new TextBox()
- };
- window.Show();
- // Do a layout and make sure that TextBox gets added to visual tree and its
- // template applied.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<TextBox>(window.Presenter.Child);
- Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
- // Clear the content and ensure the TextBox is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
- }
- }
- [Fact]
- public void TextBox_With_Xaml_Binding_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var window = new Window
- {
- DataContext = new Node { Name = "foo" },
- Content = new TextBox()
- };
- var binding = new Avalonia.Data.Binding
- {
- Path = "Name"
- };
- var textBox = (TextBox)window.Content;
- textBox.Bind(TextBox.TextProperty, binding);
- window.Show();
- // Do a layout and make sure that TextBox gets added to visual tree and its
- // Text property set.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<TextBox>(window.Presenter.Child);
- Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
- // Clear the content and DataContext and ensure the TextBox is removed.
- window.Content = null;
- window.DataContext = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Node>()).ObjectsCount));
- }
- }
- [Fact]
- public void TextBox_Class_Listeners_Are_Freed()
- {
- using (Start())
- {
- TextBox textBox;
- var window = new Window
- {
- Content = textBox = new TextBox()
- };
- window.Show();
- // Do a layout and make sure that TextBox gets added to visual tree and its
- // template applied.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Same(textBox, window.Presenter.Child);
- // Get the border from the TextBox template.
- var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border");
- // The TextBox should have subscriptions to its Classes collection from the
- // default theme.
- Assert.NotEmpty(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
- // Clear the content and ensure the TextBox is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- // Check that the TextBox has no subscriptions to its Classes collection.
- Assert.Null(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
- }
- }
- [Fact]
- public void TreeView_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var nodes = new[]
- {
- new Node
- {
- Children = new[] { new Node() },
- }
- };
- TreeView target;
- var window = new Window
- {
- Content = target = new TreeView
- {
- DataTemplates =
- {
- new FuncTreeDataTemplate<Node>(
- (x, _) => new TextBlock { Text = x.Name },
- x => x.Children)
- },
- Items = nodes
- }
- };
- window.Show();
- // Do a layout and make sure that TreeViewItems get realized.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Single(target.ItemContainerGenerator.Containers);
- // Clear the content and ensure the TreeView is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
- }
- }
- [Fact]
- public void Slider_Is_Freed()
- {
- using (Start())
- {
- Func<Window> run = () =>
- {
- var window = new Window
- {
- Content = new Slider()
- };
- window.Show();
- // Do a layout and make sure that Slider gets added to visual tree.
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<Slider>(window.Presenter.Child);
- // Clear the content and ensure the Slider is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
- }
- }
- [Fact]
- public void RendererIsDisposed()
- {
- using (Start())
- {
- var renderer = new Mock<IRenderer>();
- renderer.Setup(x => x.Dispose());
- var impl = new Mock<IWindowImpl>();
- impl.SetupGet(x => x.RenderScaling).Returns(1);
- impl.SetupProperty(x => x.Closed);
- impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
- impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
- AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
- .ToConstant(new MockWindowingPlatform(() => impl.Object));
- var window = new Window()
- {
- Content = new Button()
- };
- window.Show();
- window.Close();
- renderer.Verify(r => r.Dispose());
- }
- }
- [Fact]
- public void Control_With_Style_RenderTransform_Is_Freed()
- {
- // # Issue #3545
- using (Start())
- {
- Func<Window> run = () =>
- {
- var window = new Window
- {
- Styles =
- {
- new Style(x => x.OfType<Canvas>())
- {
- Setters =
- {
- new Setter
- {
- Property = Visual.RenderTransformProperty,
- Value = new RotateTransform(45),
- }
- }
- }
- },
- Content = new Canvas()
- };
- window.Show();
- // Do a layout and make sure that Canvas gets added to visual tree with
- // its render transform.
- window.LayoutManager.ExecuteInitialLayoutPass();
- var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
- Assert.IsType<RotateTransform>(canvas.RenderTransform);
- // Clear the content and ensure the Canvas is removed.
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
- }
- }
- [Fact]
- public void Attached_ContextMenu_Is_Freed()
- {
- using (Start())
- {
- void AttachShowAndDetachContextMenu(Control control)
- {
- var contextMenu = new ContextMenu
- {
- Items = new[]
- {
- new MenuItem { Header = "Foo" },
- new MenuItem { Header = "Foo" },
- }
- };
- control.ContextMenu = contextMenu;
- contextMenu.Open(control);
- contextMenu.Close();
- control.ContextMenu = null;
- }
- var window = new Window();
- window.Show();
- Assert.Same(window, FocusManager.Instance.Current);
- AttachShowAndDetachContextMenu(window);
- Mock.Get(window.PlatformImpl).ResetCalls();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
- }
- }
- [Fact]
- public void Standalone_ContextMenu_Is_Freed()
- {
- using (Start())
- {
- void BuildAndShowContextMenu(Control control)
- {
- var contextMenu = new ContextMenu
- {
- Items = new[]
- {
- new MenuItem { Header = "Foo" },
- new MenuItem { Header = "Foo" },
- }
- };
- contextMenu.Open(control);
- contextMenu.Close();
- }
- var window = new Window();
- window.Show();
- Assert.Same(window, FocusManager.Instance.Current);
- BuildAndShowContextMenu(window);
- BuildAndShowContextMenu(window);
- Mock.Get(window.PlatformImpl).ResetCalls();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
- }
- }
- [Fact]
- public void Path_Is_Freed()
- {
- using (Start())
- {
- var geometry = new EllipseGeometry { Rect = new Rect(0, 0, 10, 10) };
- Func<Window> run = () =>
- {
- var window = new Window
- {
- Content = new Path
- {
- Data = geometry
- }
- };
- window.Show();
- window.LayoutManager.ExecuteInitialLayoutPass();
- Assert.IsType<Path>(window.Presenter.Child);
- window.Content = null;
- window.LayoutManager.ExecuteLayoutPass();
- Assert.Null(window.Presenter.Child);
- return window;
- };
- var result = run();
- dotMemory.Check(memory =>
- Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Path>()).ObjectsCount));
- // We are keeping geometry alive to simulate a resource that outlives the control.
- GC.KeepAlive(geometry);
- }
- }
- private IDisposable Start()
- {
- return UnitTestApplication.Start(TestServices.StyledWindow.With(
- focusManager: new FocusManager(),
- keyboardDevice: () => new KeyboardDevice(),
- inputManager: new InputManager()));
- }
- private class Node
- {
- public string Name { get; set; }
- public IEnumerable<Node> Children { get; set; }
- }
- private class NullRenderer : IRenderer
- {
- public bool DrawFps { get; set; }
- public bool DrawDirtyRects { get; set; }
- public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
- public void AddDirty(IVisual visual)
- {
- }
- public void Dispose()
- {
- }
- public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) => null;
- public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) => null;
- public void Paint(Rect rect)
- {
- }
- public void RecalculateChildren(IVisual visual)
- {
- }
- public void Resized(Size size)
- {
- }
- public void Start()
- {
- }
- public void Stop()
- {
- }
- }
- }
- }
|