Explorar o código

Merge pull request #1038 from AvaloniaUI/fixes/1035-reenable-skipped-tests

Fix intermittently failing Avalonia.Markup.UnitTests
Steven Kirk %!s(int64=8) %!d(string=hai) anos
pai
achega
23f39b8f45

+ 1 - 1
src/Avalonia.Base/Data/BindingNotification.cs

@@ -226,7 +226,7 @@ namespace Avalonia.Data
         /// </summary>
         public void ClearValue()
         {
-            _value = null;
+            _value = AvaloniaProperty.UnsetValue;
         }
 
         /// <summary>

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs

@@ -102,7 +102,7 @@ namespace Avalonia.Markup.Xaml.Data
 
         private object ConvertValue(IList<object> values, Type targetType)
         {
-            var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentUICulture);
+            var converted = Converter.Convert(values, targetType, null, CultureInfo.CurrentCulture);
 
             if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null)
             {

+ 2 - 2
src/Markup/Avalonia.Markup/Data/BindingExpression.cs

@@ -122,7 +122,7 @@ namespace Avalonia.Markup.Data
                         value,
                         type,
                         ConverterParameter,
-                        CultureInfo.CurrentUICulture);
+                        CultureInfo.CurrentCulture);
 
                     if (converted == AvaloniaProperty.UnsetValue)
                     {
@@ -186,7 +186,7 @@ namespace Avalonia.Markup.Data
                     value,
                     _targetType,
                     ConverterParameter,
-                    CultureInfo.CurrentUICulture);
+                    CultureInfo.CurrentCulture);
 
                 notification = converted as BindingNotification;
 

+ 1 - 1
tests/Avalonia.Base.UnitTests/Properties/AssemblyInfo.cs

@@ -7,4 +7,4 @@ using Xunit;
 [assembly: AssemblyTitle("Avalonia.UnitTests")]
 
 // Don't run tests in parallel.
-[assembly: CollectionBehavior(MaxParallelThreads = 1)]
+[assembly: CollectionBehavior(DisableTestParallelization = true)]

+ 2 - 1
tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Reactive.Linq;
+using System.Threading.Tasks;
 using Avalonia.Controls;
 using Avalonia.UnitTests;
 using Xunit;
@@ -13,7 +14,7 @@ namespace Avalonia.Markup.UnitTests
     public class ControlLocatorTests
     {
         [Fact]
-        public async void Track_By_Name_Should_Find_Control_Added_Earlier()
+        public async Task Track_By_Name_Should_Find_Control_Added_Earlier()
         {
             TextBlock target;
             TextBlock relativeTo;

+ 58 - 16
tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Reactive.Linq;
 using System.Threading;
+using System.Threading.Tasks;
 using Avalonia.Data;
 using Avalonia.Markup.Data;
 using Avalonia.UnitTests;
@@ -17,13 +18,15 @@ namespace Avalonia.Markup.UnitTests.Data
     public class BindingExpressionTests : IClassFixture<InvariantCultureFixture>
     {
         [Fact]
-        public async void Should_Get_Simple_Property_Value()
+        public async Task Should_Get_Simple_Property_Value()
         {
             var data = new Class1 { StringValue = "foo" };
             var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(string));
             var result = await target.Take(1);
 
             Assert.Equal("foo", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -35,6 +38,8 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext("bar");
 
             Assert.Equal("bar", data.StringValue);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -46,36 +51,44 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext("bar");
 
             Assert.Equal("bar", data.Foo[0]);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Convert_Get_String_To_Double()
+        public async Task Should_Convert_Get_String_To_Double()
         {
             var data = new Class1 { StringValue = "5.6" };
             var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
             var result = await target.Take(1);
 
             Assert.Equal(5.6, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Getting_Invalid_Double_String_Should_Return_BindingError()
+        public async Task Getting_Invalid_Double_String_Should_Return_BindingError()
         {
             var data = new Class1 { StringValue = "foo" };
             var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
             var result = await target.Take(1);
 
             Assert.IsType<BindingNotification>(result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Coerce_Get_Null_Double_String_To_UnsetValue()
+        public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue()
         {
             var data = new Class1 { StringValue = null };
             var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -87,16 +100,20 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext(6.7);
 
             Assert.Equal((6.7).ToString(), data.StringValue);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Convert_Get_Double_To_String()
+        public async Task Should_Convert_Get_Double_To_String()
         {
             var data = new Class1 { DoubleValue = 5.6 };
             var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
             var result = await target.Take(1);
 
             Assert.Equal((5.6).ToString(), result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -108,10 +125,12 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext("6.7");
 
             Assert.Equal(6.7, data.DoubleValue);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value()
+        public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value()
         {
             var data = new Class1 { StringValue = "foo" };
             var target = new BindingExpression(
@@ -127,10 +146,12 @@ namespace Avalonia.Markup.UnitTests.Data
                     BindingErrorType.Error,
                     42),
                 result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation()
+        public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation()
         {
             var data = new Class1 { StringValue = "foo" };
             var target = new BindingExpression(
@@ -146,10 +167,12 @@ namespace Avalonia.Markup.UnitTests.Data
                     BindingErrorType.Error,
                     42),
                 result);
+
+            GC.KeepAlive(data);
         }
 
-        [Fact(Skip="Result is not always AggregateException.")]
-        public async void Should_Return_BindingNotification_For_Invalid_FallbackValue()
+        [Fact]
+        public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue()
         {
             var data = new Class1 { StringValue = "foo" };
             var target = new BindingExpression(
@@ -166,10 +189,12 @@ namespace Avalonia.Markup.UnitTests.Data
                         new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
                     BindingErrorType.Error),
                 result);
+
+            GC.KeepAlive(data);
         }
 
-        [Fact(Skip="Result is not always AggregateException.")]
-        public async void Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
+        [Fact]
+        public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
         {
             var data = new Class1 { StringValue = "foo" };
             var target = new BindingExpression(
@@ -186,6 +211,8 @@ namespace Avalonia.Markup.UnitTests.Data
                         new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
                     BindingErrorType.Error),
                 result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -197,6 +224,8 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext("foo");
 
             Assert.Equal(5.6, data.DoubleValue);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -212,6 +241,8 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext("foo");
 
             Assert.Equal(9.8, data.DoubleValue);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -223,6 +254,8 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext(null);
 
             Assert.Equal(0, data.DoubleValue);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -234,13 +267,16 @@ namespace Avalonia.Markup.UnitTests.Data
             target.OnNext(AvaloniaProperty.UnsetValue);
 
             Assert.Equal(0, data.DoubleValue);
+
+            GC.KeepAlive(data);
         }
 
-        [Fact(Skip="Moq.MockException")]
+        [Fact]
         public void Should_Pass_ConverterParameter_To_Convert()
         {
             var data = new Class1 { DoubleValue = 5.6 };
             var converter = new Mock<IValueConverter>();
+
             var target = new BindingExpression(
                 new ExpressionObserver(data, "DoubleValue"),
                 typeof(string),
@@ -249,10 +285,12 @@ namespace Avalonia.Markup.UnitTests.Data
 
             target.Subscribe(_ => { });
 
-            converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.InvariantCulture));
+            converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentCulture));
+
+            GC.KeepAlive(data);
         }
 
-        [Fact(Skip="Moq.MockException")]
+        [Fact]
         public void Should_Pass_ConverterParameter_To_ConvertBack()
         {
             var data = new Class1 { DoubleValue = 5.6 };
@@ -265,10 +303,12 @@ namespace Avalonia.Markup.UnitTests.Data
 
             target.OnNext("bar");
 
-            converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.InvariantCulture));
+            converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentCulture));
+
+            GC.KeepAlive(data);
         }
 
-        [Fact(Skip="Moq.MockException")]
+        [Fact]
         public void Should_Handle_DataValidation()
         {
             var data = new Class1 { DoubleValue = 5.6 };
@@ -292,6 +332,8 @@ namespace Avalonia.Markup.UnitTests.Data
                         BindingErrorType.Error)
                 },
                 result);
+
+            GC.KeepAlive(data);
         }
 
         private class Class1 : NotifyingBase

+ 3 - 2
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AttachedProperty.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Reactive.Linq;
+using System.Threading.Tasks;
 using Avalonia.Diagnostics;
 using Avalonia.Markup.Data;
 using Xunit;
@@ -18,7 +19,7 @@ namespace Avalonia.Markup.UnitTests.Data
         }
 
         [Fact]
-        public async void Should_Get_Attached_Property_Value()
+        public async Task Should_Get_Attached_Property_Value()
         {
             var data = new Class1();
             var target = new ExpressionObserver(data, "(Owner.Foo)");
@@ -30,7 +31,7 @@ namespace Avalonia.Markup.UnitTests.Data
         }
 
         [Fact]
-        public async void Should_Get_Chained_Attached_Property_Value()
+        public async Task Should_Get_Chained_Attached_Property_Value()
         {
             var data = new Class1
             {

+ 2 - 1
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_AvaloniaProperty.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Reactive.Linq;
+using System.Threading.Tasks;
 using Avalonia.Diagnostics;
 using Avalonia.Markup.Data;
 using Xunit;
@@ -18,7 +19,7 @@ namespace Avalonia.Markup.UnitTests.Data
         }
 
         [Fact]
-        public async void Should_Get_Simple_Property_Value()
+        public async Task Should_Get_Simple_Property_Value()
         {
             var data = new Class1();
             var target = new ExpressionObserver(data, "Foo");

+ 9 - 0
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_DataValidation.cs

@@ -28,6 +28,8 @@ namespace Avalonia.Markup.UnitTests.Data
             observer.SetValue(-5);
 
             Assert.False(validationMessageFound);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -43,6 +45,8 @@ namespace Avalonia.Markup.UnitTests.Data
             observer.SetValue(-5);
 
             Assert.True(validationMessageFound);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -102,6 +106,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, 5),
                 new BindingNotification(5),
             }, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -147,6 +153,9 @@ namespace Avalonia.Markup.UnitTests.Data
                     BindingErrorType.Error,
                     AvaloniaProperty.UnsetValue),
             }, result);
+
+            GC.KeepAlive(container);
+            GC.KeepAlive(inner);
         }
 
         public class ExceptionTest : NotifyingBase

+ 56 - 11
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Reactive.Linq;
+using System.Threading.Tasks;
 using Avalonia.Collections;
 using Avalonia.Diagnostics;
 using Avalonia.Markup.Data;
@@ -16,113 +17,135 @@ namespace Avalonia.Markup.UnitTests.Data
     public class ExpressionObserverTests_Indexer
     {
         [Fact]
-        public async void Should_Get_Array_Value()
+        public async Task Should_Get_Array_Value()
         {
             var data = new { Foo = new [] { "foo", "bar" } };
             var target = new ExpressionObserver(data, "Foo[1]");
             var result = await target.Take(1);
 
             Assert.Equal("bar", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_UnsetValue_For_Invalid_Array_Index()
+        public async Task Should_Get_UnsetValue_For_Invalid_Array_Index()
         {
             var data = new { Foo = new[] { "foo", "bar" } };
             var target = new ExpressionObserver(data, "Foo[invalid]");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_UnsetValue_For_Invalid_Dictionary_Index()
+        public async Task Should_Get_UnsetValue_For_Invalid_Dictionary_Index()
         {
             var data = new { Foo = new Dictionary<int, string> { { 1, "foo" } } };
             var target = new ExpressionObserver(data, "Foo[invalid]");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_UnsetValue_For_Object_Without_Indexer()
+        public async Task Should_Get_UnsetValue_For_Object_Without_Indexer()
         {
             var data = new { Foo = 5 };
             var target = new ExpressionObserver(data, "Foo[noindexer]");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_MultiDimensional_Array_Value()
+        public async Task Should_Get_MultiDimensional_Array_Value()
         {
             var data = new { Foo = new[,] { { "foo", "bar" }, { "baz", "qux" } } };
             var target = new ExpressionObserver(data, "Foo[1, 1]");
             var result = await target.Take(1);
 
             Assert.Equal("qux", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_Value_For_String_Indexer()
+        public async Task Should_Get_Value_For_String_Indexer()
         {
             var data = new { Foo = new Dictionary<string, string> { { "foo", "bar" }, { "baz", "qux" } } };
             var target = new ExpressionObserver(data, "Foo[foo]");
             var result = await target.Take(1);
 
             Assert.Equal("bar", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_Value_For_Non_String_Indexer()
+        public async Task Should_Get_Value_For_Non_String_Indexer()
         {
             var data = new { Foo = new Dictionary<double, string> { { 1.0, "bar" }, { 2.0, "qux" } } };
             var target = new ExpressionObserver(data, "Foo[1.0]");
             var result = await target.Take(1);
 
             Assert.Equal("bar", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Array_Out_Of_Bounds_Should_Return_UnsetValue()
+        public async Task Array_Out_Of_Bounds_Should_Return_UnsetValue()
         {
             var data = new { Foo = new[] { "foo", "bar" } };
             var target = new ExpressionObserver(data, "Foo[2]");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Array_With_Wrong_Dimensions_Should_Return_UnsetValue()
+        public async Task Array_With_Wrong_Dimensions_Should_Return_UnsetValue()
         {
             var data = new { Foo = new[] { "foo", "bar" } };
             var target = new ExpressionObserver(data, "Foo[1,2]");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void List_Out_Of_Bounds_Should_Return_UnsetValue()
+        public async Task List_Out_Of_Bounds_Should_Return_UnsetValue()
         {
             var data = new { Foo = new List<string> { "foo", "bar" } };
             var target = new ExpressionObserver(data, "Foo[2]");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_List_Value()
+        public async Task Should_Get_List_Value()
         {
             var data = new { Foo = new List<string> { "foo", "bar" } };
             var target = new ExpressionObserver(data, "Foo[1]");
             var result = await target.Take(1);
 
             Assert.Equal("bar", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -139,6 +162,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
             Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result);
             Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -155,6 +180,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
             Assert.Equal(new[] { "foo", "bar" }, result);
             Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -171,6 +198,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
             Assert.Equal(new[] { "bar", "baz" }, result);
             Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -187,6 +216,9 @@ namespace Avalonia.Markup.UnitTests.Data
             data.Foo.Move(0, 1);
 
             Assert.Equal(new[] { "bar", "foo" }, result);
+
+            GC.KeepAlive(sub);
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -200,6 +232,9 @@ namespace Avalonia.Markup.UnitTests.Data
             data.Foo.Clear();
 
             Assert.Equal(new[] { "bar", AvaloniaProperty.UnsetValue }, result);
+
+            GC.KeepAlive(sub);
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -220,6 +255,8 @@ namespace Avalonia.Markup.UnitTests.Data
             var expected = new[] { "bar", "bar2" };
             Assert.Equal(expected, result);
             Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -234,6 +271,8 @@ namespace Avalonia.Markup.UnitTests.Data
             }
 
             Assert.Equal("baz", data.Foo[1]);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -254,6 +293,8 @@ namespace Avalonia.Markup.UnitTests.Data
             }
 
             Assert.Equal(4, data.Foo["foo"]);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -274,6 +315,8 @@ namespace Avalonia.Markup.UnitTests.Data
             }
 
             Assert.Equal(4, data.Foo["bar"]);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -291,6 +334,8 @@ namespace Avalonia.Markup.UnitTests.Data
             }
             
             Assert.Equal("bar2", data.Foo["foo"]);
+
+            GC.KeepAlive(data);
         }
 
         private class NonIntegerIndexer : NotifyingBase

+ 4 - 1
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs

@@ -90,7 +90,8 @@ namespace Avalonia.Markup.UnitTests.Data
         {
             var scheduler = new TestScheduler();
             var update = scheduler.CreateColdObservable<Unit>();
-            var target = new ExpressionObserver(() => new { Foo = "foo" }, "Foo", update);
+            var data = new { Foo = "foo" };
+            var target = new ExpressionObserver(() => data, "Foo", update);
             var result = new List<object>();
 
             using (target.Subscribe(x => result.Add(x)))
@@ -101,6 +102,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
             Assert.Equal(new[] { "foo" }, result);
             Assert.All(update.Subscriptions, x => Assert.NotEqual(Subscription.Infinite, x.Unsubscribe));
+
+            GC.KeepAlive(data);
         }
 
         private Recorded<Notification<object>> OnNext(long time, object value)

+ 24 - 7
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Reactive.Linq;
+using System.Threading.Tasks;
 using Avalonia.Data;
 using Avalonia.Markup.Data;
 using Xunit;
@@ -12,57 +13,67 @@ namespace Avalonia.Markup.UnitTests.Data
     public class ExpressionObserverTests_Negation
     {
         [Fact]
-        public async void Should_Negate_Boolean_Value()
+        public async Task Should_Negate_Boolean_Value()
         {
             var data = new { Foo = true };
             var target = new ExpressionObserver(data, "!Foo");
             var result = await target.Take(1);
 
             Assert.Equal(false, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Negate_0()
+        public async Task Should_Negate_0()
         {
             var data = new { Foo = 0 };
             var target = new ExpressionObserver(data, "!Foo");
             var result = await target.Take(1);
 
             Assert.Equal(true, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Negate_1()
+        public async Task Should_Negate_1()
         {
             var data = new { Foo = 1 };
             var target = new ExpressionObserver(data, "!Foo");
             var result = await target.Take(1);
 
             Assert.Equal(false, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Negate_False_String()
+        public async Task Should_Negate_False_String()
         {
             var data = new { Foo = "false" };
             var target = new ExpressionObserver(data, "!Foo");
             var result = await target.Take(1);
 
             Assert.Equal(true, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Negate_True_String()
+        public async Task Should_Negate_True_String()
         {
             var data = new { Foo = "True" };
             var target = new ExpressionObserver(data, "!Foo");
             var result = await target.Take(1);
 
             Assert.Equal(false, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_BindingNotification_For_String_Not_Convertible_To_Boolean()
+        public async Task Should_Return_BindingNotification_For_String_Not_Convertible_To_Boolean()
         {
             var data = new { Foo = "foo" };
             var target = new ExpressionObserver(data, "!Foo");
@@ -73,10 +84,12 @@ namespace Avalonia.Markup.UnitTests.Data
                     new InvalidCastException($"Unable to convert 'foo' to bool."),
                     BindingErrorType.Error), 
                 result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_BindingNotification_For_Value_Not_Convertible_To_Boolean()
+        public async Task Should_Return_BindingNotification_For_Value_Not_Convertible_To_Boolean()
         {
             var data = new { Foo = new object() };
             var target = new ExpressionObserver(data, "!Foo");
@@ -87,6 +100,8 @@ namespace Avalonia.Markup.UnitTests.Data
                     new InvalidCastException($"Unable to convert 'System.Object' to bool."),
                     BindingErrorType.Error),
                 result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -96,6 +111,8 @@ namespace Avalonia.Markup.UnitTests.Data
             var target = new ExpressionObserver(data, "!Foo");
 
             Assert.False(target.SetValue("bar"));
+
+            GC.KeepAlive(data);
         }
     }
 }

+ 12 - 0
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs

@@ -29,6 +29,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 sync.ExecutePostedCallbacks();
 
                 Assert.Equal(new[] { source }, result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -47,6 +49,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 sync.ExecutePostedCallbacks();
 
                 Assert.Equal(new[] { "foo", "bar" }, result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -67,6 +71,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
                 sub.Dispose();
                 Assert.Equal(0, data.PropertyChangedSubscriptionCount);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -87,6 +93,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 // What does it mean to have data validation on an observable? Without a use-case
                 // it's hard to know what to do here so for the moment the value is returned.
                 Assert.Equal(new[] { "foo", "bar" }, result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -107,6 +115,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
                 sub.Dispose();
                 Assert.Equal(0, data.PropertyChangedSubscriptionCount);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -132,6 +142,8 @@ namespace Avalonia.Markup.UnitTests.Data
                     result);
 
                 sub.Dispose();
+
+                GC.KeepAlive(data);
             }
         }
 

+ 69 - 9
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs

@@ -11,19 +11,22 @@ using Avalonia.Data;
 using Avalonia.Markup.Data;
 using Avalonia.UnitTests;
 using Xunit;
+using System.Threading.Tasks;
 
 namespace Avalonia.Markup.UnitTests.Data
 {
     public class ExpressionObserverTests_Property
     {
         [Fact]
-        public async void Should_Get_Simple_Property_Value()
+        public async Task Should_Get_Simple_Property_Value()
         {
             var data = new { Foo = "foo" };
             var target = new ExpressionObserver(data, "Foo");
             var result = await target.Take(1);
 
             Assert.Equal("foo", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -35,76 +38,92 @@ namespace Avalonia.Markup.UnitTests.Data
             target.Subscribe(_ => { });
 
             Assert.Equal(typeof(string), target.ResultType);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_Simple_Property_Value_Null()
+        public async Task Should_Get_Simple_Property_Value_Null()
         {
             var data = new { Foo = (string)null };
             var target = new ExpressionObserver(data, "Foo");
             var result = await target.Take(1);
 
             Assert.Null(result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_Simple_Property_From_Base_Class()
+        public async Task Should_Get_Simple_Property_From_Base_Class()
         {
             var data = new Class3 { Foo = "foo" };
             var target = new ExpressionObserver(data, "Foo");
             var result = await target.Take(1);
 
             Assert.Equal("foo", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_UnsetValue_For_Root_Null()
+        public async Task Should_Return_UnsetValue_For_Root_Null()
         {
             var data = new Class3 { Foo = "foo" };
             var target = new ExpressionObserver(default(object), "Foo");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_UnsetValue_For_Root_UnsetValue()
+        public async Task Should_Return_UnsetValue_For_Root_UnsetValue()
         {
             var data = new Class3 { Foo = "foo" };
             var target = new ExpressionObserver(AvaloniaProperty.UnsetValue, "Foo");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_UnsetValue_For_Observable_Root_Null()
+        public async Task Should_Return_UnsetValue_For_Observable_Root_Null()
         {
             var data = new Class3 { Foo = "foo" };
             var target = new ExpressionObserver(Observable.Return(default(object)), "Foo");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_UnsetValue_For_Observable_Root_UnsetValue()
+        public async Task Should_Return_UnsetValue_For_Observable_Root_UnsetValue()
         {
             var data = new Class3 { Foo = "foo" };
             var target = new ExpressionObserver(Observable.Return(AvaloniaProperty.UnsetValue), "Foo");
             var result = await target.Take(1);
 
             Assert.Equal(AvaloniaProperty.UnsetValue, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Get_Simple_Property_Chain()
+        public async Task Should_Get_Simple_Property_Chain()
         {
             var data = new { Foo = new { Bar = new { Baz = "baz" } }  };
             var target = new ExpressionObserver(data, "Foo.Bar.Baz");
             var result = await target.Take(1);
 
             Assert.Equal("baz", result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -116,10 +135,12 @@ namespace Avalonia.Markup.UnitTests.Data
             target.Subscribe(_ => { });
 
             Assert.Equal(typeof(string), target.ResultType);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
-        public async void Should_Return_BindingNotification_Error_For_Broken_Chain()
+        public async Task Should_Return_BindingNotification_Error_For_Broken_Chain()
         {
             var data = new { Foo = new { Bar = 1 } };
             var target = new ExpressionObserver(data, "Foo.Bar.Baz");
@@ -131,6 +152,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 new BindingNotification(
                     new MissingMemberException("Could not find CLR property 'Baz' on '1'"), BindingErrorType.Error),
                 result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -151,6 +174,8 @@ namespace Avalonia.Markup.UnitTests.Data
                         AvaloniaProperty.UnsetValue),
                 },
                 result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -160,6 +185,8 @@ namespace Avalonia.Markup.UnitTests.Data
             var target = new ExpressionObserver(data, "Foo.Bar.Baz");
 
             Assert.Null(target.ResultType);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -177,6 +204,8 @@ namespace Avalonia.Markup.UnitTests.Data
             sub.Dispose();
 
             Assert.Equal(0, data.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -205,6 +234,8 @@ namespace Avalonia.Markup.UnitTests.Data
             sub.Dispose();
 
             Assert.Equal(0, data.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -224,6 +255,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
             Assert.Equal(0, data.PropertyChangedSubscriptionCount);
             Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -245,6 +278,8 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal(0, data.PropertyChangedSubscriptionCount);
             Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
             Assert.Equal(0, old.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -286,6 +321,8 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal(0, data.PropertyChangedSubscriptionCount);
             Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
             Assert.Equal(0, old.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -318,6 +355,8 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
             Assert.Equal(0, breaking.PropertyChangedSubscriptionCount);
             Assert.Equal(0, old.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -334,6 +373,8 @@ namespace Avalonia.Markup.UnitTests.Data
             update.OnNext(Unit.Default);
 
             Assert.Equal(new[] { "foo", "bar" }, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -374,6 +415,8 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal(new[] { "foo", "bar" }, result1);
             Assert.Equal(new[] { "foo", "bar" }, result2);
             Assert.Equal(new[] { "bar" }, result3);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -391,6 +434,8 @@ namespace Avalonia.Markup.UnitTests.Data
             sub2.Dispose();
 
             Assert.Equal(0, data.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -405,6 +450,8 @@ namespace Avalonia.Markup.UnitTests.Data
             }
 
             Assert.Equal("bar", data.Foo);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -419,6 +466,8 @@ namespace Avalonia.Markup.UnitTests.Data
             }
 
             Assert.Equal("baz", ((Class2)data.Next).Bar);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -431,6 +480,8 @@ namespace Avalonia.Markup.UnitTests.Data
             {
                 Assert.False(target.SetValue("baz"));
             }
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -444,6 +495,8 @@ namespace Avalonia.Markup.UnitTests.Data
             target.SetValue("bar");
 
             Assert.Equal(new[] { null, "bar" }, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -457,6 +510,8 @@ namespace Avalonia.Markup.UnitTests.Data
             target.SetValue("bar");
 
             Assert.Equal(new[] { null, "bar" }, result);
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -469,6 +524,8 @@ namespace Avalonia.Markup.UnitTests.Data
             {
                 Assert.False(target.SetValue("baz"));
             }
+
+            GC.KeepAlive(data);
         }
 
         [Fact]
@@ -498,6 +555,9 @@ namespace Avalonia.Markup.UnitTests.Data
 
             Assert.Equal(0, first.PropertyChangedSubscriptionCount);
             Assert.Equal(0, second.PropertyChangedSubscriptionCount);
+
+            GC.KeepAlive(first);
+            GC.KeepAlive(second);
         }
 
         [Fact]

+ 12 - 0
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs

@@ -30,6 +30,8 @@ namespace Avalonia.Markup.UnitTests.Data
 
                 Assert.Equal(1, result.Count);
                 Assert.IsType<Task<string>>(result[0]);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -45,6 +47,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 var sub = target.Subscribe(x => result.Add(x));
 
                 Assert.Equal(new[] { "foo" }, result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -63,6 +67,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 sync.ExecutePostedCallbacks();
 
                 Assert.Equal(new[] { "foo" }, result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -88,6 +94,8 @@ namespace Avalonia.Markup.UnitTests.Data
                             BindingErrorType.Error)
                     }, 
                     result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -110,6 +118,8 @@ namespace Avalonia.Markup.UnitTests.Data
                             BindingErrorType.Error)
                     },
                     result);
+
+                GC.KeepAlive(data);
             }
         }
 
@@ -130,6 +140,8 @@ namespace Avalonia.Markup.UnitTests.Data
                 // What does it mean to have data validation on a Task? Without a use-case it's
                 // hard to know what to do here so for the moment the value is returned.
                 Assert.Equal(new [] { "foo" }, result);
+
+                GC.KeepAlive(data);
             }
         }
 

+ 2 - 0
tests/Avalonia.Markup.UnitTests/Data/Plugins/ExceptionValidationPluginTests.cs

@@ -35,6 +35,8 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
                 new BindingNotification(new ArgumentOutOfRangeException("value"), BindingErrorType.DataValidationError),
                 new BindingNotification(6),
             }, result);
+
+            GC.KeepAlive(data);
         }
 
         public class Data : NotifyingBase

+ 2 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

@@ -10,13 +10,14 @@ using Moq;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml.Data;
 using Xunit;
+using System.Threading.Tasks;
 
 namespace Avalonia.Markup.Xaml.UnitTests.Data
 {
     public class MultiBindingTests
     {
         [Fact]
-        public async void OneWay_Binding_Should_Be_Set_Up()
+        public async Task OneWay_Binding_Should_Be_Set_Up()
         {
             var source = new { A = 1, B = 2, C = 3 };
             var binding = new MultiBinding

+ 2 - 1
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reactive;
 using System.Reactive.Linq;
+using System.Threading.Tasks;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Data;
@@ -45,7 +46,7 @@ namespace Avalonia.Styling.UnitTests
         }
 
         [Fact]
-        public async void Child_Matches_Control_When_It_Is_Child_OfType_And_Class()
+        public async Task Child_Matches_Control_When_It_Is_Child_OfType_And_Class()
         {
             var parent = new TestLogical1();
             var child = new TestLogical2();

+ 6 - 6
tests/Avalonia.UnitTests/InvariantCultureFixture.cs

@@ -21,20 +21,20 @@ namespace Avalonia.UnitTests
         public InvariantCultureFixture()
         {
 #if NET461
-            _restore = Thread.CurrentThread.CurrentUICulture;
-            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
+            _restore = Thread.CurrentThread.CurrentCulture;
+            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
 #else
-            _restore = CultureInfo.CurrentUICulture;
-            CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+            _restore = CultureInfo.CurrentCulture;
+            CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
 #endif
         }
 
         public void Dispose()
         {
 #if NET461
-            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = _restore;
+            Thread.CurrentThread.CurrentCulture = _restore;
 #else
-            CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = _restore;
+            CultureInfo.CurrentCulture = _restore;
 #endif
         }
     }