diff --git a/scripts/ReplaceNugetCache.ps1 b/scripts/ReplaceNugetCache.ps1 index 6de50f978d..70f5eaa40b 100644 --- a/scripts/ReplaceNugetCache.ps1 +++ b/scripts/ReplaceNugetCache.ps1 @@ -1,6 +1,5 @@ -copy ..\samples\ControlCatalog.Desktop\bin\Debug\net461\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\net461\ copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\ copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\ -copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Gtk3.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\ copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\ copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\ +copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\ diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index f6bb73129f..925fe10681 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1190,10 +1190,6 @@ namespace Metsys.Bson object container = null; var property = typeHelper.FindProperty(name); var propertyType = property != null ? property.Type : _typeMap.ContainsKey(storageType) ? _typeMap[storageType] : typeof(object); - if (property == null && typeHelper.Expando == null) - { - throw new BsonException(string.Format("Deserialization failed: type {0} does not have a property named {1}", type.FullName, name)); - } if (property != null && property.Setter == null) { container = property.Getter(instance); @@ -1201,7 +1197,8 @@ namespace Metsys.Bson var value = isNull ? null : DeserializeValue(propertyType, storageType, container, options); if (property == null) { - ((IDictionary)typeHelper.Expando.Getter(instance))[name] = value; + if (typeHelper.Expando != null) + ((IDictionary)typeHelper.Expando.Getter(instance))[name] = value; } else if (container == null && value != null && !property.Ignored) { diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs index 562dbdf8f9..d01265c9f4 100644 --- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -46,7 +46,7 @@ namespace Avalonia.Remote.Protocol { try { - var cl = await server.AcceptTcpClientAsync(); + var cl = await server.AcceptTcpClientAsync().ConfigureAwait(false); AcceptNew(); await Task.Run(async () => { @@ -54,7 +54,7 @@ namespace Avalonia.Remote.Protocol var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0)); cb(t); await tcs.Task; - }); + }).ConfigureAwait(false); } catch { @@ -69,7 +69,7 @@ namespace Avalonia.Remote.Protocol public async Task Connect(IPAddress address, int port) { var c = new TcpClient(); - await c.ConnectAsync(address, port); + await c.ConnectAsync(address, port).ConfigureAwait(false); return CreateTransport(_resolver, c.GetStream(), ((IDisposable)c).Dispose); } } diff --git a/tests/Avalonia.DesignerSupport.Tests/Helpers.cs b/tests/Avalonia.DesignerSupport.Tests/Helpers.cs new file mode 100644 index 0000000000..223a86a9af --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/Helpers.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Avalonia.DesignerSupport.Tests +{ + static class Helpers + { + public static void StructDiff(object parsed, object expected) => StructDiff(parsed, expected, "{root}"); + + static void StructDiff(object parsed, object expected, string path) + { + if (parsed == null && expected == null) + return; + if ((parsed == null && expected != null) || (parsed != null && expected == null)) + throw new Exception( + $"{path}: Null mismatch: {(parsed == null ? "null" : "not-null")} {(expected == null ? "null" : "not-null")}"); + + if (parsed.GetType() != expected.GetType()) + throw new Exception($"{path}: Type mismatch: {parsed.GetType()} {expected.GetType()}"); + + if (parsed is string || parsed.GetType().IsPrimitive) + { + if (!parsed.Equals(expected)) + throw new Exception($"{path}: Not equal {parsed} {expected}"); + } + else if (parsed is IDictionary dic) + { + var dic2 = (IDictionary) expected; + if (dic.Count != dic2.Count) + throw new Exception($"{path}: Dictionary count mismatch: {dic.Count} {dic2.Count}"); + + foreach (var k in dic.Keys.Cast().OrderBy(o => o.ToString())) + { + var v1 = dic[k]; + var v2 = dic2[k]; + StructDiff(v1, v2, path + "['" + k + "']"); + } + } + else if (parsed is IList col) + { + var col2 = (IList) expected; + if (col.Count != col2.Count) + throw new Exception($"{path}: Collection count mismatch: {col.Count} {col2.Count}"); + for (var c = 0; c < col.Count; c++) + StructDiff(col[c], col2[c], path + "[" + c + "]"); + } + else + { + foreach (var prop in parsed.GetType().GetProperties() + .Where(p => p.GetMethod != null && p.GetMethod.IsPublic)) + { + StructDiff(prop.GetValue(parsed), prop.GetValue(expected), path + "." + prop.Name); + } + } + + + + } + } +} diff --git a/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs b/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs new file mode 100644 index 0000000000..e5a477cc32 --- /dev/null +++ b/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Viewport; +using Xunit; + +namespace Avalonia.DesignerSupport.Tests +{ + public class RemoteProtocolTests : IDisposable + { + private readonly List _disposables = new List(); + private IAvaloniaRemoteTransportConnection _server; + private IAvaloniaRemoteTransportConnection _client; + private BlockingCollection _serverMessages = new BlockingCollection(); + private BlockingCollection _clientMessages = new BlockingCollection(); + private SynchronizationContext _originalContext; + + + class DisabledSyncContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object state) + { + throw new InvalidCastException("Not allowed"); + } + + public override void Send(SendOrPostCallback d, object state) + { + throw new InvalidCastException("Not allowed"); + } + } + + void Init(IMessageTypeResolver clientResolver = null, IMessageTypeResolver serverResolver = null) + { + _originalContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(new DisabledSyncContext()); + var clientTransport = new BsonTcpTransport(clientResolver ?? new DefaultMessageTypeResolver()); + var serverTransport = new BsonTcpTransport(serverResolver ?? new DefaultMessageTypeResolver()); + + var tcpListener = new TcpListener(IPAddress.Loopback, 0); + tcpListener.Start(); + var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port; + tcpListener.Stop(); + + var tcs = new TaskCompletionSource(); + serverTransport.Listen(IPAddress.Loopback, port, connected => + { + _server = connected; + tcs.SetResult(0); + }); + _client = clientTransport.Connect(IPAddress.Loopback, port).Result; + _disposables.Add(_client); + _client.OnMessage += (_, m) => _clientMessages.Add(m); + tcs.Task.Wait(); + _disposables.Add(_server); + _server.OnMessage += (_, m) => _serverMessages.Add(m); + + } + + object TakeServer() + { + var src = new CancellationTokenSource(200); + try + { + return _serverMessages.Take(src.Token); + } + finally + { + src.Dispose(); + } + + } + + [Fact] + void EntitiesAreProperlySerializedAndDeserialized() + { + Init(); + var rnd = new Random(); + _server.OnMessage += (_, message) => { }; + + + object GetRandomValue(Type t, string pathInfo) + { + if (t.IsArray) + { + var arr = Array.CreateInstance(t.GetElementType(), 1); + ((IList)arr)[0] = GetRandomValue(t.GetElementType(), pathInfo); + return arr; + } + + if (t == typeof(bool)) + return true; + if (t == typeof(int) || t == typeof(long)) + return rnd.Next(); + if (t == typeof(byte)) + return (byte)rnd.Next(255); + if (t == typeof(double)) + return rnd.NextDouble(); + if (t.IsEnum) + return ((IList)Enum.GetValues(t)).Cast().Last(); + if (t == typeof(string)) + return Guid.NewGuid().ToString(); + if (t == typeof(Guid)) + return Guid.NewGuid(); + throw new Exception($"Doesn't know how to fabricate a random value for {t}, path {pathInfo}"); + } + + foreach (var t in typeof(MeasureViewportMessage).Assembly.GetTypes().Where(t => + t.GetCustomAttribute(typeof(AvaloniaRemoteMessageGuidAttribute)) != null)) + { + var o = Activator.CreateInstance(t); + foreach (var p in t.GetProperties()) + p.SetValue(o, GetRandomValue(p.PropertyType, $"{t.FullName}.{p.Name}")); + + _client.Send(o).Wait(200); + var received = TakeServer(); + Helpers.StructDiff(received, o); + + } + + + } + + [Fact] + void RemoteProtocolShouldBeBackwardsCompatible() + { + Init(new DefaultMessageTypeResolver(typeof(ExtendedMeasureViewportMessage).Assembly)); + _client.Send(new ExtendedMeasureViewportMessage() + { + Width = 100, Height = 200, SomeNewProperty = 300, + SomeArrayProperty = new[]{1,2,3}, + SubObjectProperty = new ExtendedMeasureViewportMessage.SubObject() + { + Foo = 543 + } + }); + var received = (MeasureViewportMessage)TakeServer(); + Assert.Equal(100, received.Width); + Assert.Equal(200, received.Height); + + } + + public void Dispose() + { + _disposables.ForEach(d => d.Dispose()); + SynchronizationContext.SetSynchronizationContext(_originalContext); + } + } + + [AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")] + public class ExtendedMeasureViewportMessage + { + public double Width { get; set; } + + public int SomeNewProperty { get; set; } + public int[] SomeArrayProperty { get; set; } + public class SubObject + { + public int Foo { get; set; } + } + public SubObject SubObjectProperty { get; set; } + public double Height { get; set; } + } +}