diff --git a/Perspex.sln b/Perspex.sln index 24c017c309..8e836d8e35 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -136,6 +136,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOS", "src\iOS\Pers EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOSTestApplication", "src\iOS\Perspex.iOSTestApplication\Perspex.iOSTestApplication.csproj", "{8C923867-8A8F-4F6B-8B80-47D9E8436166}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.LeakTests", "tests\Perspex.LeakTests\Perspex.LeakTests.csproj", "{E1AA3DBF-9056-4530-9376-18119A7A3FFE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 @@ -157,6 +159,7 @@ Global src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{e1aa3dbf-9056-4530-9376-18119a7a3ffe}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -1240,6 +1243,30 @@ Global {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.Build.0 = Release|iPhone {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|Any CPU.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhone.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhone.Build.0 = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|Any CPU.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhone.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhone.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1282,5 +1309,6 @@ Global {FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} {4488AD85-1495-4809-9AA4-DDFE0A48527E} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} {8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} + {E1AA3DBF-9056-4530-9376-18119A7A3FFE} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs new file mode 100644 index 0000000000..557f04f794 --- /dev/null +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -0,0 +1,106 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.dotMemoryUnit; +using Perspex.Controls; +using Perspex.Controls.Templates; +using Xunit; +using Xunit.Abstractions; + +namespace Perspex.LeakTests +{ + [DotMemoryUnit(FailIfRunWithoutSupport = false)] + public class ControlTests + { + public ControlTests(ITestOutputHelper atr) + { + TestApp.Initialize(); + DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); + } + + [Fact] + public void Canvas_Is_Freed() + { + Func run = () => + { + var window = new Window + { + Content = new Canvas() + }; + + // Do a layout and make sure that Canvas gets added to visual tree. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(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()).ObjectsCount)); + } + + [Fact] + public void TreeView_Is_Freed() + { + Func run = () => + { + var nodes = new[] + { + new Node + { + Children = new[] { new Node() }, + } + }; + + TreeView target; + + var window = new Window + { + Content = target = new TreeView + { + DataTemplates = new DataTemplates + { + new FuncTreeDataTemplate( + x => new TextBlock { Text = x.Name }, + x => x.Children, + x => true) + }, + Items = nodes + } + }; + + // Do a layout and make sure that TreeViewItems get realized. + window.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(1, target.ItemContainerGenerator.Containers.Count()); + + // 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()).ObjectsCount)); + } + + private class Node + { + public string Name { get; set; } + public IEnumerable Children { get; set; } + } + } +} diff --git a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj new file mode 100644 index 0000000000..b2d67554c3 --- /dev/null +++ b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj @@ -0,0 +1,174 @@ + + + + + + Debug + AnyCPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE} + Library + Properties + Perspex.LeakTests + Perspex.LeakTests + v4.5 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\JetBrains.dotMemoryUnit.2.1.20150828.125449\lib\dotMemory.Unit.dll + True + + + ..\..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll + True + + + ..\..\packages\AutoFixture.3.31.3\lib\net40\Ploeh.AutoFixture.dll + True + + + ..\..\packages\AutoFixture.AutoMoq.3.31.1\lib\net40\Ploeh.AutoFixture.AutoMoq.dll + True + + + + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + True + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll + True + + + + + + + + + + + + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Perspex.Themes.Default + + + {5ccb5571-7c30-4e7d-967d-0e2158ebd91f} + Perspex.Controls.UnitTests + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs b/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..cb943198bc --- /dev/null +++ b/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.LeakTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.LeakTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e1aa3dbf-9056-4530-9376-18119a7a3ffe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Perspex.LeakTests/Readme.txt b/tests/Perspex.LeakTests/Readme.txt new file mode 100644 index 0000000000..2a6087b5b3 --- /dev/null +++ b/tests/Perspex.LeakTests/Readme.txt @@ -0,0 +1,8 @@ +Memory Leak Tests +----------------- + +These tests use JetBrains' dotMemory Unit. When run in a normal test runner, they will always pass. + +To run the tests, you need to have dotMemory/ReSharper and install the XUnit plugin. You should +then be able to run the tests using Resharper -> Unit Tests -> Run all tests from solution under +dotMemory Unit. \ No newline at end of file diff --git a/tests/Perspex.LeakTests/TestApp.cs b/tests/Perspex.LeakTests/TestApp.cs new file mode 100644 index 0000000000..d0df7c18fc --- /dev/null +++ b/tests/Perspex.LeakTests/TestApp.cs @@ -0,0 +1,41 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Moq; +using Perspex.Controls.UnitTests; +using Perspex.Platform; +using Perspex.Shared.PlatformSupport; +using Perspex.Themes.Default; +using Ploeh.AutoFixture; +using Ploeh.AutoFixture.AutoMoq; + +namespace Perspex.LeakTests +{ + internal class TestApp : Application + { + private TestApp() + { + RegisterServices(); + + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var windowImpl = new Mock(); + var renderInterface = fixture.Create(); + + PerspexLocator.CurrentMutable + .Bind().ToConstant(new AssetLoader()) + .Bind().ToConstant(new PclPlatformWrapper()) + .Bind().ToConstant(renderInterface) + .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); + + Styles = new DefaultTheme(); + } + + public static void Initialize() + { + if (Current == null) + { + new TestApp(); + } + } + } +} diff --git a/tests/Perspex.LeakTests/app.config b/tests/Perspex.LeakTests/app.config new file mode 100644 index 0000000000..4ecf794192 --- /dev/null +++ b/tests/Perspex.LeakTests/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.LeakTests/packages.config b/tests/Perspex.LeakTests/packages.config new file mode 100644 index 0000000000..35e293bdbc --- /dev/null +++ b/tests/Perspex.LeakTests/packages.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file