From ef81c5596051ac123a46c9165b455c48d3bcb06d Mon Sep 17 00:00:00 2001 From: donandren Date: Fri, 17 Feb 2017 00:32:38 +0200 Subject: [PATCH] another simple unit tests for issue #855 for direct and styled properties --- .../Avalonia.Base.UnitTests.csproj | 4 + .../AvaloniaObjectTests_Binding.cs | 103 +++++++++++++++-- .../AvaloniaObjectTests_Direct.cs | 106 ++++++++++++++++-- 3 files changed, 191 insertions(+), 22 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 07ed7f14ca..c712a8bcf9 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -124,6 +124,10 @@ {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Avalonia.Base + + {3E53A01A-B331-47F3-B828-4A5717E77A24} + Avalonia.Markup.Xaml + {88060192-33d5-4932-b0f9-8bd2763e857d} Avalonia.UnitTests diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 5e286305d2..7186207b92 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -2,22 +2,21 @@ // 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.ComponentModel; +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; -using Microsoft.Reactive.Testing; +using System.Threading; +using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Logging; -using Avalonia.UnitTests; -using Xunit; -using System.Threading.Tasks; +using Avalonia.Markup.Xaml.Data; using Avalonia.Platform; -using System.Threading; -using Moq; -using System.Reactive.Disposables; -using System.Reactive.Concurrency; using Avalonia.Threading; +using Avalonia.UnitTests; +using Microsoft.Reactive.Testing; +using Moq; +using Xunit; namespace Avalonia.Base.UnitTests { @@ -363,7 +362,7 @@ namespace Avalonia.Base.UnitTests Assert.True(called); } } - + [Fact] public async void Bind_With_Scheduler_Executes_On_Scheduler() { @@ -384,7 +383,43 @@ namespace Avalonia.Base.UnitTests await Task.Run(() => source.OnNext(6.7)); } + } + + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new Class1(); + + //note: if the initialization of the child binding is here target/child binding work fine!!! + //var child = new Class1() + //{ + // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + //}; + target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new Class1() + { + [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + }; + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); } /// @@ -405,6 +440,15 @@ namespace Avalonia.Base.UnitTests public static readonly StyledProperty QuxProperty = AvaloniaProperty.Register("Qux", 5.6); + + public static readonly StyledProperty DoubleValueProperty = + AvaloniaProperty.Register(nameof(DoubleValue)); + + public double DoubleValue + { + get { return GetValue(DoubleValueProperty); } + set { SetValue(DoubleValueProperty, value); } + } } private class Class2 : Class1 @@ -431,5 +475,40 @@ namespace Avalonia.Base.UnitTests return new InstancedBinding(_source, BindingMode.OneTime); } } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } -} +} \ No newline at end of file diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index ecb555252d..b6e5396020 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Reactive.Subjects; -using Avalonia; using Avalonia.Data; using Avalonia.Logging; +using Avalonia.Markup.Xaml.Data; using Avalonia.UnitTests; using Xunit; @@ -208,7 +209,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - Assert.Throws(() => + Assert.Throws(() => target.SetValue(Class1.BarProperty, "newvalue")); } @@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests { var target = new Class1(); - Assert.Throws(() => + Assert.Throws(() => target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue")); } @@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new Subject(); - Assert.Throws(() => + Assert.Throws(() => target.Bind(Class1.BarProperty, source)); } @@ -439,12 +440,49 @@ namespace Avalonia.Base.UnitTests Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().DefaultBindingMode); } + [Fact] + public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values() + { + var viewModel = new TestStackOverflowViewModel() + { + Value = 50 + }; + + var target = new Class1(); + + //note: if the initialization of the child binding is here there is no stackoverflow!!! + //var child = new Class1() + //{ + // [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + //}; + + target.Bind(Class1.DoubleValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel }); + + var child = new Class1() + { + [~~Class1.DoubleValueProperty] = target[~~Class1.DoubleValueProperty] + }; + + Assert.Equal(1, viewModel.SetterInvokedCount); + + //here in real life stack overflow exception is thrown issue #855 and #824 + target.DoubleValue = 51.001; + + Assert.Equal(2, viewModel.SetterInvokedCount); + + double expected = 51; + + Assert.Equal(expected, viewModel.Value); + Assert.Equal(expected, target.DoubleValue); + Assert.Equal(expected, child.DoubleValue); + } + private class Class1 : AvaloniaObject { public static readonly DirectProperty FooProperty = AvaloniaProperty.RegisterDirect( - "Foo", - o => o.Foo, + "Foo", + o => o.Foo, (o, v) => o.Foo = v, unsetValue: "unset"); @@ -453,14 +491,21 @@ namespace Avalonia.Base.UnitTests public static readonly DirectProperty BazProperty = AvaloniaProperty.RegisterDirect( - "Bar", - o => o.Baz, - (o,v) => o.Baz = v, + "Bar", + o => o.Baz, + (o, v) => o.Baz = v, unsetValue: -1); + public static readonly DirectProperty DoubleValueProperty = + AvaloniaProperty.RegisterDirect( + nameof(DoubleValue), + o => o.DoubleValue, + (o, v) => o.DoubleValue = v); + private string _foo = "initial"; private readonly string _bar = "bar"; private int _baz = 5; + private double _doubleValue; public string Foo { @@ -478,6 +523,12 @@ namespace Avalonia.Base.UnitTests get { return _baz; } set { SetAndRaise(BazProperty, ref _baz, value); } } + + public double DoubleValue + { + get { return _doubleValue; } + set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); } + } } private class Class2 : AvaloniaObject @@ -497,5 +548,40 @@ namespace Avalonia.Base.UnitTests set { SetAndRaise(FooProperty, ref _foo, value); } } } + + private class TestStackOverflowViewModel : INotifyPropertyChanged + { + public int SetterInvokedCount { get; private set; } + + public const int MaxInvokedCount = 1000; + + private double _value; + + public event PropertyChangedEventHandler PropertyChanged; + + public double Value + { + get { return _value; } + set + { + if (_value != value) + { + SetterInvokedCount++; + if (SetterInvokedCount < MaxInvokedCount) + { + _value = (int)value; + if (_value > 75) _value = 75; + if (_value < 25) _value = 25; + } + else + { + _value = value; + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); + } + } + } + } } -} +} \ No newline at end of file