ControlTests.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Runtime.Remoting.Contexts;
  5. using Avalonia.Controls;
  6. using Avalonia.Controls.Shapes;
  7. using Avalonia.Controls.Templates;
  8. using Avalonia.Diagnostics;
  9. using Avalonia.Input;
  10. using Avalonia.Layout;
  11. using Avalonia.Media;
  12. using Avalonia.Platform;
  13. using Avalonia.Rendering;
  14. using Avalonia.Styling;
  15. using Avalonia.UnitTests;
  16. using Avalonia.VisualTree;
  17. using JetBrains.dotMemoryUnit;
  18. using Moq;
  19. using Xunit;
  20. using Xunit.Abstractions;
  21. namespace Avalonia.LeakTests
  22. {
  23. [DotMemoryUnit(FailIfRunWithoutSupport = false)]
  24. public class ControlTests
  25. {
  26. public ControlTests(ITestOutputHelper atr)
  27. {
  28. DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
  29. }
  30. [Fact]
  31. public void Canvas_Is_Freed()
  32. {
  33. using (Start())
  34. {
  35. Func<Window> run = () =>
  36. {
  37. var window = new Window
  38. {
  39. Content = new Canvas()
  40. };
  41. window.Show();
  42. // Do a layout and make sure that Canvas gets added to visual tree.
  43. window.LayoutManager.ExecuteInitialLayoutPass();
  44. Assert.IsType<Canvas>(window.Presenter.Child);
  45. // Clear the content and ensure the Canvas is removed.
  46. window.Content = null;
  47. window.LayoutManager.ExecuteLayoutPass();
  48. Assert.Null(window.Presenter.Child);
  49. return window;
  50. };
  51. var result = run();
  52. dotMemory.Check(memory =>
  53. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  54. }
  55. }
  56. [Fact]
  57. public void Named_Canvas_Is_Freed()
  58. {
  59. using (Start())
  60. {
  61. Func<Window> run = () =>
  62. {
  63. var scope = new NameScope();
  64. var window = new Window
  65. {
  66. Content = new Canvas
  67. {
  68. Name = "foo"
  69. }.RegisterInNameScope(scope)
  70. };
  71. NameScope.SetNameScope(window, scope);
  72. window.Show();
  73. // Do a layout and make sure that Canvas gets added to visual tree.
  74. window.LayoutManager.ExecuteInitialLayoutPass();
  75. Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
  76. Assert.IsType<Canvas>(window.Presenter.Child);
  77. // Clear the content and ensure the Canvas is removed.
  78. window.Content = null;
  79. NameScope.SetNameScope(window, null);
  80. window.LayoutManager.ExecuteLayoutPass();
  81. Assert.Null(window.Presenter.Child);
  82. return window;
  83. };
  84. var result = run();
  85. dotMemory.Check(memory =>
  86. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  87. }
  88. }
  89. [Fact]
  90. public void ScrollViewer_With_Content_Is_Freed()
  91. {
  92. using (Start())
  93. {
  94. Func<Window> run = () =>
  95. {
  96. var window = new Window
  97. {
  98. Content = new ScrollViewer
  99. {
  100. Content = new Canvas()
  101. }
  102. };
  103. window.Show();
  104. // Do a layout and make sure that ScrollViewer gets added to visual tree and its
  105. // template applied.
  106. window.LayoutManager.ExecuteInitialLayoutPass();
  107. Assert.IsType<ScrollViewer>(window.Presenter.Child);
  108. Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
  109. // Clear the content and ensure the ScrollViewer is removed.
  110. window.Content = null;
  111. window.LayoutManager.ExecuteLayoutPass();
  112. Assert.Null(window.Presenter.Child);
  113. return window;
  114. };
  115. var result = run();
  116. dotMemory.Check(memory =>
  117. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  118. dotMemory.Check(memory =>
  119. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  120. }
  121. }
  122. [Fact]
  123. public void TextBox_Is_Freed()
  124. {
  125. using (Start())
  126. {
  127. Func<Window> run = () =>
  128. {
  129. var window = new Window
  130. {
  131. Content = new TextBox()
  132. };
  133. window.Show();
  134. // Do a layout and make sure that TextBox gets added to visual tree and its
  135. // template applied.
  136. window.LayoutManager.ExecuteInitialLayoutPass();
  137. Assert.IsType<TextBox>(window.Presenter.Child);
  138. Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
  139. // Clear the content and ensure the TextBox is removed.
  140. window.Content = null;
  141. window.LayoutManager.ExecuteLayoutPass();
  142. Assert.Null(window.Presenter.Child);
  143. return window;
  144. };
  145. var result = run();
  146. dotMemory.Check(memory =>
  147. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  148. }
  149. }
  150. [Fact]
  151. public void TextBox_With_Xaml_Binding_Is_Freed()
  152. {
  153. using (Start())
  154. {
  155. Func<Window> run = () =>
  156. {
  157. var window = new Window
  158. {
  159. DataContext = new Node { Name = "foo" },
  160. Content = new TextBox()
  161. };
  162. var binding = new Avalonia.Data.Binding
  163. {
  164. Path = "Name"
  165. };
  166. var textBox = (TextBox)window.Content;
  167. textBox.Bind(TextBox.TextProperty, binding);
  168. window.Show();
  169. // Do a layout and make sure that TextBox gets added to visual tree and its
  170. // Text property set.
  171. window.LayoutManager.ExecuteInitialLayoutPass();
  172. Assert.IsType<TextBox>(window.Presenter.Child);
  173. Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
  174. // Clear the content and DataContext and ensure the TextBox is removed.
  175. window.Content = null;
  176. window.DataContext = null;
  177. window.LayoutManager.ExecuteLayoutPass();
  178. Assert.Null(window.Presenter.Child);
  179. return window;
  180. };
  181. var result = run();
  182. dotMemory.Check(memory =>
  183. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  184. dotMemory.Check(memory =>
  185. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Node>()).ObjectsCount));
  186. }
  187. }
  188. [Fact]
  189. public void TextBox_Class_Listeners_Are_Freed()
  190. {
  191. using (Start())
  192. {
  193. TextBox textBox;
  194. var window = new Window
  195. {
  196. Content = textBox = new TextBox()
  197. };
  198. window.Show();
  199. // Do a layout and make sure that TextBox gets added to visual tree and its
  200. // template applied.
  201. window.LayoutManager.ExecuteInitialLayoutPass();
  202. Assert.Same(textBox, window.Presenter.Child);
  203. // Get the border from the TextBox template.
  204. var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border");
  205. // The TextBox should have subscriptions to its Classes collection from the
  206. // default theme.
  207. Assert.NotEmpty(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  208. // Clear the content and ensure the TextBox is removed.
  209. window.Content = null;
  210. window.LayoutManager.ExecuteLayoutPass();
  211. Assert.Null(window.Presenter.Child);
  212. // Check that the TextBox has no subscriptions to its Classes collection.
  213. Assert.Null(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  214. }
  215. }
  216. [Fact]
  217. public void TreeView_Is_Freed()
  218. {
  219. using (Start())
  220. {
  221. Func<Window> run = () =>
  222. {
  223. var nodes = new[]
  224. {
  225. new Node
  226. {
  227. Children = new[] { new Node() },
  228. }
  229. };
  230. TreeView target;
  231. var window = new Window
  232. {
  233. Content = target = new TreeView
  234. {
  235. DataTemplates =
  236. {
  237. new FuncTreeDataTemplate<Node>(
  238. (x, _) => new TextBlock { Text = x.Name },
  239. x => x.Children)
  240. },
  241. Items = nodes
  242. }
  243. };
  244. window.Show();
  245. // Do a layout and make sure that TreeViewItems get realized.
  246. window.LayoutManager.ExecuteInitialLayoutPass();
  247. Assert.Single(target.ItemContainerGenerator.Containers);
  248. // Clear the content and ensure the TreeView is removed.
  249. window.Content = null;
  250. window.LayoutManager.ExecuteLayoutPass();
  251. Assert.Null(window.Presenter.Child);
  252. return window;
  253. };
  254. var result = run();
  255. dotMemory.Check(memory =>
  256. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
  257. }
  258. }
  259. [Fact]
  260. public void Slider_Is_Freed()
  261. {
  262. using (Start())
  263. {
  264. Func<Window> run = () =>
  265. {
  266. var window = new Window
  267. {
  268. Content = new Slider()
  269. };
  270. window.Show();
  271. // Do a layout and make sure that Slider gets added to visual tree.
  272. window.LayoutManager.ExecuteInitialLayoutPass();
  273. Assert.IsType<Slider>(window.Presenter.Child);
  274. // Clear the content and ensure the Slider is removed.
  275. window.Content = null;
  276. window.LayoutManager.ExecuteLayoutPass();
  277. Assert.Null(window.Presenter.Child);
  278. return window;
  279. };
  280. var result = run();
  281. dotMemory.Check(memory =>
  282. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
  283. }
  284. }
  285. [Fact]
  286. public void RendererIsDisposed()
  287. {
  288. using (Start())
  289. {
  290. var renderer = new Mock<IRenderer>();
  291. renderer.Setup(x => x.Dispose());
  292. var impl = new Mock<IWindowImpl>();
  293. impl.SetupGet(x => x.RenderScaling).Returns(1);
  294. impl.SetupProperty(x => x.Closed);
  295. impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  296. impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
  297. AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
  298. .ToConstant(new MockWindowingPlatform(() => impl.Object));
  299. var window = new Window()
  300. {
  301. Content = new Button()
  302. };
  303. window.Show();
  304. window.Close();
  305. renderer.Verify(r => r.Dispose());
  306. }
  307. }
  308. [Fact]
  309. public void Control_With_Style_RenderTransform_Is_Freed()
  310. {
  311. // # Issue #3545
  312. using (Start())
  313. {
  314. Func<Window> run = () =>
  315. {
  316. var window = new Window
  317. {
  318. Styles =
  319. {
  320. new Style(x => x.OfType<Canvas>())
  321. {
  322. Setters =
  323. {
  324. new Setter
  325. {
  326. Property = Visual.RenderTransformProperty,
  327. Value = new RotateTransform(45),
  328. }
  329. }
  330. }
  331. },
  332. Content = new Canvas()
  333. };
  334. window.Show();
  335. // Do a layout and make sure that Canvas gets added to visual tree with
  336. // its render transform.
  337. window.LayoutManager.ExecuteInitialLayoutPass();
  338. var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
  339. Assert.IsType<RotateTransform>(canvas.RenderTransform);
  340. // Clear the content and ensure the Canvas is removed.
  341. window.Content = null;
  342. window.LayoutManager.ExecuteLayoutPass();
  343. Assert.Null(window.Presenter.Child);
  344. return window;
  345. };
  346. var result = run();
  347. dotMemory.Check(memory =>
  348. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  349. }
  350. }
  351. [Fact]
  352. public void Attached_ContextMenu_Is_Freed()
  353. {
  354. using (Start())
  355. {
  356. void AttachShowAndDetachContextMenu(Control control)
  357. {
  358. var contextMenu = new ContextMenu
  359. {
  360. Items = new[]
  361. {
  362. new MenuItem { Header = "Foo" },
  363. new MenuItem { Header = "Foo" },
  364. }
  365. };
  366. control.ContextMenu = contextMenu;
  367. contextMenu.Open(control);
  368. contextMenu.Close();
  369. control.ContextMenu = null;
  370. }
  371. var window = new Window();
  372. window.Show();
  373. Assert.Same(window, FocusManager.Instance.Current);
  374. // Context menu in resources means the baseline may not be 0.
  375. var initialMenuCount = 0;
  376. var initialMenuItemCount = 0;
  377. dotMemory.Check(memory =>
  378. {
  379. initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
  380. initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
  381. });
  382. AttachShowAndDetachContextMenu(window);
  383. Mock.Get(window.PlatformImpl).Invocations.Clear();
  384. dotMemory.Check(memory =>
  385. Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
  386. dotMemory.Check(memory =>
  387. Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
  388. }
  389. }
  390. [Fact]
  391. public void Standalone_ContextMenu_Is_Freed()
  392. {
  393. using (Start())
  394. {
  395. void BuildAndShowContextMenu(Control control)
  396. {
  397. var contextMenu = new ContextMenu
  398. {
  399. Items = new[]
  400. {
  401. new MenuItem { Header = "Foo" },
  402. new MenuItem { Header = "Foo" },
  403. }
  404. };
  405. contextMenu.Open(control);
  406. contextMenu.Close();
  407. }
  408. var window = new Window();
  409. window.Show();
  410. Assert.Same(window, FocusManager.Instance.Current);
  411. // Context menu in resources means the baseline may not be 0.
  412. var initialMenuCount = 0;
  413. var initialMenuItemCount = 0;
  414. dotMemory.Check(memory =>
  415. {
  416. initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
  417. initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
  418. });
  419. BuildAndShowContextMenu(window);
  420. BuildAndShowContextMenu(window);
  421. Mock.Get(window.PlatformImpl).Invocations.Clear();
  422. dotMemory.Check(memory =>
  423. Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
  424. dotMemory.Check(memory =>
  425. Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
  426. }
  427. }
  428. [Fact]
  429. public void Path_Is_Freed()
  430. {
  431. using (Start())
  432. {
  433. var geometry = new EllipseGeometry { Rect = new Rect(0, 0, 10, 10) };
  434. Func<Window> run = () =>
  435. {
  436. var window = new Window
  437. {
  438. Content = new Path
  439. {
  440. Data = geometry
  441. }
  442. };
  443. window.Show();
  444. window.LayoutManager.ExecuteInitialLayoutPass();
  445. Assert.IsType<Path>(window.Presenter.Child);
  446. window.Content = null;
  447. window.LayoutManager.ExecuteLayoutPass();
  448. Assert.Null(window.Presenter.Child);
  449. return window;
  450. };
  451. var result = run();
  452. dotMemory.Check(memory =>
  453. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Path>()).ObjectsCount));
  454. // We are keeping geometry alive to simulate a resource that outlives the control.
  455. GC.KeepAlive(geometry);
  456. }
  457. }
  458. [Fact]
  459. public void ItemsRepeater_Is_Freed()
  460. {
  461. using (Start())
  462. {
  463. Func<Window> run = () =>
  464. {
  465. var window = new Window
  466. {
  467. Content = new ItemsRepeater(),
  468. };
  469. window.Show();
  470. window.LayoutManager.ExecuteInitialLayoutPass();
  471. Assert.IsType<ItemsRepeater>(window.Presenter.Child);
  472. window.Content = null;
  473. window.LayoutManager.ExecuteLayoutPass();
  474. Assert.Null(window.Presenter.Child);
  475. return window;
  476. };
  477. var result = run();
  478. dotMemory.Check(memory =>
  479. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ItemsRepeater>()).ObjectsCount));
  480. }
  481. }
  482. private IDisposable Start()
  483. {
  484. return UnitTestApplication.Start(TestServices.StyledWindow.With(
  485. focusManager: new FocusManager(),
  486. keyboardDevice: () => new KeyboardDevice(),
  487. inputManager: new InputManager()));
  488. }
  489. private class Node
  490. {
  491. public string Name { get; set; }
  492. public IEnumerable<Node> Children { get; set; }
  493. }
  494. private class NullRenderer : IRenderer
  495. {
  496. public bool DrawFps { get; set; }
  497. public bool DrawDirtyRects { get; set; }
  498. #pragma warning disable CS0067
  499. public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
  500. #pragma warning restore CS0067
  501. public void AddDirty(IVisual visual)
  502. {
  503. }
  504. public void Dispose()
  505. {
  506. }
  507. public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) => null;
  508. public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) => null;
  509. public void Paint(Rect rect)
  510. {
  511. }
  512. public void RecalculateChildren(IVisual visual)
  513. {
  514. }
  515. public void Resized(Size size)
  516. {
  517. }
  518. public void Start()
  519. {
  520. }
  521. public void Stop()
  522. {
  523. }
  524. }
  525. }
  526. }