From 870a2f365f6409a8940616ccad5baa36dc067886 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 12 Feb 2019 16:45:04 +0200 Subject: [PATCH 01/11] layoutmanager/listbox/vistualization/scroll issue unit test --- .../ListBoxTests.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index b6f7c9ec96..6939c6a081 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -267,6 +267,71 @@ namespace Avalonia.Controls.UnitTests Assert.True(true); } + [Fact] + public void LayoutManager_Should_Measure_Arrange_All() + { + var virtualizationMode = ItemVirtualizationMode.Simple; + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new AvaloniaList(Enumerable.Range(1, 7).Select(v => v.ToString())); + + var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight }; + + wnd.IsVisible = true; + + var target = new ListBox(); + + wnd.Content = target; + + var lm = wnd.LayoutManager; + + target.Height = 110; + target.Width = 50; + target.DataContext = items; + target.VirtualizationMode = virtualizationMode; + + target.ItemTemplate = new FuncDataTemplate(c => + { + var tb = new TextBlock() { Height = 10, Width = 30 }; + tb.Bind(TextBlock.TextProperty, new Data.Binding()); + return tb; + }, true); + + lm.ExecuteInitialLayoutPass(wnd); + + target.Items = items; + + lm.ExecuteLayoutPass(); + + items.Insert(3, "3+"); + lm.ExecuteLayoutPass(); + + items.Insert(4, "4+"); + lm.ExecuteLayoutPass(); + + //RESET + items.Clear(); + foreach (var i in Enumerable.Range(1, 7)) + { + items.Add(i.ToString()); + } + + //working bit better with this line no outof memory or remaining to arrange/measure ??? + //lm.ExecuteLayoutPass(); + + items.Insert(2, "2+"); + + lm.ExecuteLayoutPass(); + + var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic; + var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable; + var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable; + + Assert.Equal(0, toMeasure.Count()); + Assert.Equal(0, toArrange.Count()); + } + } + private FuncControlTemplate ListBoxTemplate() { return new FuncControlTemplate(parent => From d53dbe9416775f46f62b3e8a0dff832cc5f7c148 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 19 Feb 2019 14:29:44 +0200 Subject: [PATCH 02/11] add some more layout manager tests for potential issues with infinity loop and proper work --- .../LayoutManagerTests.cs | 119 ++++++++++++++++-- 1 file changed, 110 insertions(+), 9 deletions(-) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index b0e8a3780e..10006609aa 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -1,11 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; -using Avalonia.UnitTests; -using System; using Xunit; -using System.Collections.Generic; namespace Avalonia.Layout.UnitTests { @@ -74,7 +73,6 @@ namespace Avalonia.Layout.UnitTests } }; - var order = new List(); Size MeasureOverride(ILayoutable control, Size size) { @@ -110,7 +108,6 @@ namespace Avalonia.Layout.UnitTests } }; - var order = new List(); Size MeasureOverride(ILayoutable control, Size size) { @@ -196,9 +193,9 @@ namespace Avalonia.Layout.UnitTests Width = 100, Height = 100, }; - + var arrangeSize = default(Size); - + root.DoArrangeOverride = (_, s) => { arrangeSize = s; @@ -207,7 +204,7 @@ namespace Avalonia.Layout.UnitTests root.LayoutManager.ExecuteInitialLayoutPass(root); Assert.Equal(new Size(100, 100), arrangeSize); - + root.Width = 120; root.LayoutManager.ExecuteLayoutPass(); @@ -238,7 +235,111 @@ namespace Avalonia.Layout.UnitTests border.Height = 100; root.LayoutManager.ExecuteLayoutPass(); - Assert.Equal(new Size(100, 100), panel.DesiredSize); + Assert.Equal(new Size(100, 100), panel.DesiredSize); + } + + [Fact] + public void LayoutManager_Should_Prevent_Infinity_Loop_On_Measure() + { + var control = new LayoutTestControl(); + var root = new LayoutTestRoot { Child = control }; + + root.LayoutManager.ExecuteInitialLayoutPass(root); + control.Measured = false; + + int cnt = 0; + int maxcnt = 100; + control.DoMeasureOverride = (l, s) => + { + //emulate a problem in the logic of a control that triggers + //invalidate measure during measure + //it can lead to infinity loop in layoutmanager + if (++cnt < maxcnt) + { + control.InvalidateMeasure(); + } + + return new Size(100, 100); + }; + + control.InvalidateMeasure(); + + root.LayoutManager.ExecuteLayoutPass(); + + Assert.True(cnt < 100); + } + + [Fact] + public void LayoutManager_Should_Prevent_Infinity_Loop_On_Arrange() + { + var control = new LayoutTestControl(); + var root = new LayoutTestRoot { Child = control }; + + root.LayoutManager.ExecuteInitialLayoutPass(root); + control.Arranged = false; + + int cnt = 0; + int maxcnt = 100; + control.DoArrangeOverride = (l, s) => + { + //emulate a problem in the logic of a control that triggers + //invalidate measure during arrange + //it can lead to infinity loop in layoutmanager + if (++cnt < maxcnt) + { + control.InvalidateArrange(); + } + + return new Size(100, 100); + }; + + control.InvalidateArrange(); + + root.LayoutManager.ExecuteLayoutPass(); + + Assert.True(cnt < 100); + } + + [Fact] + public void LayoutManager_Should_Properly_Arrange_Visuals_Even_There_Are_Issues_With_Previous_Arranged() + { + var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray(); + var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray(); + + StackPanel panel; + + var root = new LayoutTestRoot + { + Child = panel = new StackPanel() + }; + + panel.Children.AddRange(nonArrageableTargets); + panel.Children.AddRange(targets); + + root.LayoutManager.ExecuteInitialLayoutPass(root); + + foreach (var c in panel.Children.OfType()) + { + c.Measured = c.Arranged = false; + c.InvalidateMeasure(); + } + + foreach (var c in nonArrageableTargets) + { + c.DoArrangeOverride = (l, s) => + { + //emulate a problem in the logic of a control that triggers + //invalidate measure during arrange + c.InvalidateMeasure(); + return new Size(100, 100); + }; + } + + root.LayoutManager.ExecuteLayoutPass(); + + //altough nonArrageableTargets has rubbish logic and can't be measured/arranged properly + //layoutmanager should process properly other visuals + Assert.All(targets, c => Assert.True(c.Arranged)); } } } From 48ad6babdabfd02a9338aa16fde1195ce36d3ef8 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 19 Feb 2019 14:31:01 +0200 Subject: [PATCH 03/11] improve test --- tests/Avalonia.Controls.UnitTests/ListBoxTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 6939c6a081..343d8d41f3 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -321,6 +321,9 @@ namespace Avalonia.Controls.UnitTests items.Insert(2, "2+"); + lm.ExecuteLayoutPass(); + //after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange + lm.ExecuteLayoutPass(); lm.ExecuteLayoutPass(); var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic; From 8c0746132349abf93caa8a479967603535e7e255 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 18 Feb 2019 17:19:12 +0200 Subject: [PATCH 04/11] LayoutManager InfinityLoop protection and safety improvements --- src/Avalonia.Layout/LayoutManager.cs | 93 ++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index f3540ea631..3d390f46e6 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using Avalonia.Logging; using Avalonia.Threading; @@ -13,8 +15,85 @@ namespace Avalonia.Layout /// public class LayoutManager : ILayoutManager { - private readonly Queue _toMeasure = new Queue(); - private readonly Queue _toArrange = new Queue(); + private class LayoutQueue : IReadOnlyCollection + { + private class Info + { + public bool Active; + public int Count; + } + + public LayoutQueue(Func shouldEnqueue) + { + _shouldEnqueue = shouldEnqueue; + } + + private Func _shouldEnqueue; + private Queue _inner = new Queue(); + private Dictionary _loopQueueInfo = new Dictionary(); + private int _maxEnqueueCountPerLoop = 1; + + public int Count => _inner.Count; + + public IEnumerator GetEnumerator() => (_inner as IEnumerable).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + + public T Dequeue() + { + var result = _inner.Dequeue(); + + if (_loopQueueInfo.TryGetValue(result, out var info)) + { + info.Active = false; + } + + return result; + } + + public void Enqueue(T item) + { + if (!_loopQueueInfo.TryGetValue(item, out var info)) + { + _loopQueueInfo[item] = info = new Info(); + } + + if (!info.Active && info.Count < _maxEnqueueCountPerLoop) + { + _inner.Enqueue(item); + info.Active = true; + info.Count++; + } + } + + public void BeginLoop(int maxEnqueueCountPerLoop) + { + _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop; + } + + public void EndLoop() + { + var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray(); + + _loopQueueInfo.Clear(); + + //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle + //one more time as a final attempt + foreach (var item in notfinalized) + { + if (_shouldEnqueue(item.Key)) + { + item.Value.Active = true; + item.Value.Count++; + _loopQueueInfo[item.Key] = item.Value; + _inner.Enqueue(item.Key); + } + } + } + } + + private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); + private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private bool _queued; private bool _running; @@ -80,6 +159,9 @@ namespace Avalonia.Layout var stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); + _toMeasure.BeginLoop(MaxPasses); + _toArrange.BeginLoop(MaxPasses); + try { for (var pass = 0; pass < MaxPasses; ++pass) @@ -98,6 +180,9 @@ namespace Avalonia.Layout _running = false; } + _toMeasure.EndLoop(); + _toArrange.EndLoop(); + stopwatch.Stop(); Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); } @@ -112,7 +197,7 @@ namespace Avalonia.Layout Arrange(root); // Running the initial layout pass may have caused some control to be invalidated - // so run a full layout pass now (this usually due to scrollbars; its not known + // so run a full layout pass now (this usually due to scrollbars; its not known // whether they will need to be shown until the layout pass has run and if the // first guess was incorrect the layout will need to be updated). ExecuteLayoutPass(); @@ -133,7 +218,7 @@ namespace Avalonia.Layout private void ExecuteArrangePass() { - while (_toArrange.Count > 0 && _toMeasure.Count == 0) + while (_toArrange.Count > 0) { var control = _toArrange.Dequeue(); From b97b3c59353dd4af4765a1077d8cfc79a731c8ee Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 19 Feb 2019 17:55:33 +0200 Subject: [PATCH 05/11] make internal stored Info a strcut to optimize memory (pr note) --- src/Avalonia.Layout/LayoutManager.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 3d390f46e6..ea84b58cbd 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -17,7 +17,7 @@ namespace Avalonia.Layout { private class LayoutQueue : IReadOnlyCollection { - private class Info + private struct Info { public bool Active; public int Count; @@ -46,6 +46,7 @@ namespace Avalonia.Layout if (_loopQueueInfo.TryGetValue(result, out var info)) { info.Active = false; + _loopQueueInfo[result] = info; } return result; @@ -53,16 +54,12 @@ namespace Avalonia.Layout public void Enqueue(T item) { - if (!_loopQueueInfo.TryGetValue(item, out var info)) - { - _loopQueueInfo[item] = info = new Info(); - } + _loopQueueInfo.TryGetValue(item, out var info); if (!info.Active && info.Count < _maxEnqueueCountPerLoop) { _inner.Enqueue(item); - info.Active = true; - info.Count++; + _loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 }; } } @@ -83,9 +80,7 @@ namespace Avalonia.Layout { if (_shouldEnqueue(item.Key)) { - item.Value.Active = true; - item.Value.Count++; - _loopQueueInfo[item.Key] = item.Value; + _loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 }; _inner.Enqueue(item.Key); } } From 6d7d051f3e717d46dde6bce80dd480190df0994c Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 19 Feb 2019 18:31:06 +0200 Subject: [PATCH 06/11] extract layoutqueue to separate file --- src/Avalonia.Layout/LayoutManager.cs | 75 -------------------------- src/Avalonia.Layout/LayoutQueue.cs | 79 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 75 deletions(-) create mode 100644 src/Avalonia.Layout/LayoutQueue.cs diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index ea84b58cbd..45efccc1fa 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -2,9 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; using Avalonia.Logging; using Avalonia.Threading; @@ -15,78 +12,6 @@ namespace Avalonia.Layout /// public class LayoutManager : ILayoutManager { - private class LayoutQueue : IReadOnlyCollection - { - private struct Info - { - public bool Active; - public int Count; - } - - public LayoutQueue(Func shouldEnqueue) - { - _shouldEnqueue = shouldEnqueue; - } - - private Func _shouldEnqueue; - private Queue _inner = new Queue(); - private Dictionary _loopQueueInfo = new Dictionary(); - private int _maxEnqueueCountPerLoop = 1; - - public int Count => _inner.Count; - - public IEnumerator GetEnumerator() => (_inner as IEnumerable).GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); - - public T Dequeue() - { - var result = _inner.Dequeue(); - - if (_loopQueueInfo.TryGetValue(result, out var info)) - { - info.Active = false; - _loopQueueInfo[result] = info; - } - - return result; - } - - public void Enqueue(T item) - { - _loopQueueInfo.TryGetValue(item, out var info); - - if (!info.Active && info.Count < _maxEnqueueCountPerLoop) - { - _inner.Enqueue(item); - _loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 }; - } - } - - public void BeginLoop(int maxEnqueueCountPerLoop) - { - _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop; - } - - public void EndLoop() - { - var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray(); - - _loopQueueInfo.Clear(); - - //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle - //one more time as a final attempt - foreach (var item in notfinalized) - { - if (_shouldEnqueue(item.Key)) - { - _loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 }; - _inner.Enqueue(item.Key); - } - } - } - } - private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private bool _queued; diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs new file mode 100644 index 0000000000..ce40fdde49 --- /dev/null +++ b/src/Avalonia.Layout/LayoutQueue.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Avalonia.Layout +{ + internal class LayoutQueue : IReadOnlyCollection + { + private struct Info + { + public bool Active; + public int Count; + } + + public LayoutQueue(Func shouldEnqueue) + { + _shouldEnqueue = shouldEnqueue; + } + + private Func _shouldEnqueue; + private Queue _inner = new Queue(); + private Dictionary _loopQueueInfo = new Dictionary(); + private int _maxEnqueueCountPerLoop = 1; + + public int Count => _inner.Count; + + public IEnumerator GetEnumerator() => (_inner as IEnumerable).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + + public T Dequeue() + { + var result = _inner.Dequeue(); + + if (_loopQueueInfo.TryGetValue(result, out var info)) + { + info.Active = false; + _loopQueueInfo[result] = info; + } + + return result; + } + + public void Enqueue(T item) + { + _loopQueueInfo.TryGetValue(item, out var info); + + if (!info.Active && info.Count < _maxEnqueueCountPerLoop) + { + _inner.Enqueue(item); + _loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 }; + } + } + + public void BeginLoop(int maxEnqueueCountPerLoop) + { + _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop; + } + + public void EndLoop() + { + var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray(); + + _loopQueueInfo.Clear(); + + //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle + //one more time as a final attempt + foreach (var item in notfinalized) + { + if (_shouldEnqueue(item.Key)) + { + _loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 }; + _inner.Enqueue(item.Key); + } + } + } + } +} From a4132890bf6a7003cc7faac4090cc90896881488 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 19 Feb 2019 18:32:15 +0200 Subject: [PATCH 07/11] add unit tests for layout queue --- .../Properties/AssemblyInfo.cs | 4 + .../LayoutQueueTests.cs | 196 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs diff --git a/src/Avalonia.Layout/Properties/AssemblyInfo.cs b/src/Avalonia.Layout/Properties/AssemblyInfo.cs index 70fc1e9330..392ad323e5 100644 --- a/src/Avalonia.Layout/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Layout/Properties/AssemblyInfo.cs @@ -1,6 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Runtime.CompilerServices; using Avalonia.Metadata; +[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")] + [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")] + diff --git a/tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs new file mode 100644 index 0000000000..f047677a8a --- /dev/null +++ b/tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs @@ -0,0 +1,196 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Avalonia.Layout.UnitTests +{ + public class LayoutQueueTests + { + [Fact] + public void Should_Enqueue() + { + var target = new LayoutQueue(_ => true); + var refQueue = new Queue(); + var items = new[] { "1", "2", "3" }; + + foreach (var item in items) + { + target.Enqueue(item); + refQueue.Enqueue(item); + } + + Assert.Equal(refQueue, target); + } + + [Fact] + public void Should_Dequeue() + { + var target = new LayoutQueue(_ => true); + var refQueue = new Queue(); + var items = new[] { "1", "2", "3" }; + + foreach (var item in items) + { + target.Enqueue(item); + refQueue.Enqueue(item); + } + + while (refQueue.Count > 0) + { + Assert.Equal(refQueue.Dequeue(), target.Dequeue()); + } + } + + [Fact] + public void Should_Enqueue_UniqueElements() + { + var target = new LayoutQueue(_ => true); + + var items = new[] { "1", "2", "3", "1" }; + + foreach (var item in items) + { + target.Enqueue(item); + } + + Assert.Equal(3, target.Count); + Assert.Equal(items.Take(3), target); + } + + [Fact] + public void Shouldnt_Enqueue_More_Than_Limit_In_Loop() + { + var target = new LayoutQueue(_ => true); + + //1 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.BeginLoop(3); + + target.Dequeue(); + + //2 + target.Enqueue("Foo"); + target.Dequeue(); + + //3 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.Dequeue(); + + //4 more than limit shouldn't be added + target.Enqueue("Foo"); + + Assert.Equal(0, target.Count); + } + + [Fact] + public void Shouldnt_Count_Unique_Enqueue_For_Limit_In_Loop() + { + var target = new LayoutQueue(_ => true); + + //1 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.BeginLoop(3); + + target.Dequeue(); + + //2 + target.Enqueue("Foo"); + target.Enqueue("Foo"); + target.Dequeue(); + + //3 + target.Enqueue("Foo"); + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.Dequeue(); + + //4 more than limit shouldn't be added + target.Enqueue("Foo"); + + Assert.Equal(0, target.Count); + } + + [Fact] + public void Should_Enqueue_When_Condition_True_After_Loop_When_Limit_Met() + { + var target = new LayoutQueue(_ => true); + + //1 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.BeginLoop(3); + + target.Dequeue(); + + //2 + target.Enqueue("Foo"); + target.Dequeue(); + + //3 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.Dequeue(); + + //4 more than limit shouldn't be added to queue + target.Enqueue("Foo"); + + Assert.Equal(0, target.Count); + + target.EndLoop(); + + //after loop should be added once + Assert.Equal(1, target.Count); + Assert.Equal("Foo", target.First()); + } + + [Fact] + public void Shouldnt_Enqueue_When_Condition_False_After_Loop_When_Limit_Met() + { + var target = new LayoutQueue(_ => false); + + //1 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.BeginLoop(3); + + target.Dequeue(); + + //2 + target.Enqueue("Foo"); + target.Dequeue(); + + //3 + target.Enqueue("Foo"); + + Assert.Equal(1, target.Count); + + target.Dequeue(); + + //4 more than limit shouldn't be added + target.Enqueue("Foo"); + + Assert.Equal(0, target.Count); + + target.EndLoop(); + + Assert.Equal(0, target.Count); + } + } +} From 61be769d61ecf869d13f528a78fc9082a8b00697 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Feb 2019 15:50:48 +0200 Subject: [PATCH 08/11] Update tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs Co-Authored-By: donandren --- tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 10006609aa..b0e2f9b393 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -239,7 +239,7 @@ namespace Avalonia.Layout.UnitTests } [Fact] - public void LayoutManager_Should_Prevent_Infinity_Loop_On_Measure() + public void LayoutManager_Should_Prevent_Infinite_Loop_On_Measure() { var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; From 260789c42506ca90384f658d06c5edcbb842d295 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Feb 2019 15:50:56 +0200 Subject: [PATCH 09/11] Update tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs Co-Authored-By: donandren --- tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index b0e2f9b393..6ecf5a7b8c 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -253,7 +253,7 @@ namespace Avalonia.Layout.UnitTests { //emulate a problem in the logic of a control that triggers //invalidate measure during measure - //it can lead to infinity loop in layoutmanager + //it can lead to an infinite loop in layoutmanager if (++cnt < maxcnt) { control.InvalidateMeasure(); From 9b4bd8d62124f552cae798f6ceed79de5048d0f5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Feb 2019 15:51:07 +0200 Subject: [PATCH 10/11] Update tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs Co-Authored-By: donandren --- tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 6ecf5a7b8c..4d7e0ee259 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -270,7 +270,7 @@ namespace Avalonia.Layout.UnitTests } [Fact] - public void LayoutManager_Should_Prevent_Infinity_Loop_On_Arrange() + public void LayoutManager_Should_Prevent_Infinite_Loop_On_Arrange() { var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; From b32a10ceda9b418cea7ff25d0593cfdae8362dfe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Feb 2019 15:51:34 +0200 Subject: [PATCH 11/11] Update tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs Co-Authored-By: donandren --- tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 4d7e0ee259..4c288b2702 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -301,7 +301,7 @@ namespace Avalonia.Layout.UnitTests } [Fact] - public void LayoutManager_Should_Properly_Arrange_Visuals_Even_There_Are_Issues_With_Previous_Arranged() + public void LayoutManager_Should_Properly_Arrange_Visuals_Even_When_There_Are_Issues_With_Previous_Arranged() { var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray(); var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();