diff --git a/test/Orleans.Serialization.UnitTests/GeneratedSerializerTests.cs b/test/Orleans.Serialization.UnitTests/GeneratedSerializerTests.cs index 01f445a15a..4cb9c3bd28 100644 --- a/test/Orleans.Serialization.UnitTests/GeneratedSerializerTests.cs +++ b/test/Orleans.Serialization.UnitTests/GeneratedSerializerTests.cs @@ -10,497 +10,521 @@ using System.Collections.Generic; using System.IO.Pipelines; using Xunit; +using System.Net.NetworkInformation; +using System.Linq; -namespace Orleans.Serialization.UnitTests +namespace Orleans.Serialization.UnitTests; + +[Trait("Category", "BVT")] +public class GeneratedSerializerTests : IDisposable { - [Trait("Category", "BVT")] - public class GeneratedSerializerTests : IDisposable - { - private readonly ServiceProvider _serviceProvider; - private readonly CodecProvider _codecProvider; - private readonly SerializerSessionPool _sessionPool; - private readonly Serializer _serializer; + private readonly ServiceProvider _serviceProvider; + private readonly CodecProvider _codecProvider; + private readonly SerializerSessionPool _sessionPool; + private readonly Serializer _serializer; + private readonly DeepCopier _deepCopier; - public GeneratedSerializerTests() - { - _serviceProvider = new ServiceCollection() - .AddSerializer() - .BuildServiceProvider(); - _codecProvider = _serviceProvider.GetRequiredService(); - _sessionPool = _serviceProvider.GetRequiredService(); - _serializer = _serviceProvider.GetRequiredService(); - } + public GeneratedSerializerTests() + { + _serviceProvider = new ServiceCollection() + .AddSerializer() + .BuildServiceProvider(); + _codecProvider = _serviceProvider.GetRequiredService(); + _sessionPool = _serviceProvider.GetRequiredService(); + _serializer = _serviceProvider.GetRequiredService(); + _deepCopier = _serviceProvider.GetRequiredService(); + } - [Fact] - public void GeneratedSerializersRoundTripThroughCodec() - { - var original = new SomeClassWithSerializers { IntField = 2, IntProperty = 30, OtherObject = MyCustomEnum.Two }; - var result = RoundTripThroughCodec(original); + [Fact] + public void GeneratedSerializersRoundTripThroughCodec() + { + var original = new SomeClassWithSerializers { IntField = 2, IntProperty = 30, OtherObject = MyCustomEnum.Two }; + var result = RoundTripThroughCodec(original); - Assert.Equal(original.IntField, result.IntField); - Assert.Equal(original.IntProperty, result.IntProperty); - var otherObj = Assert.IsType(result.OtherObject); - Assert.Equal(MyCustomEnum.Two, otherObj); - } + Assert.Equal(original.IntField, result.IntField); + Assert.Equal(original.IntProperty, result.IntProperty); + var otherObj = Assert.IsType(result.OtherObject); + Assert.Equal(MyCustomEnum.Two, otherObj); + } - [Fact] - public void GeneratedRecordSerializersRoundTripThroughCodec() + [Fact] + public void GeneratedRecordSerializersRoundTripThroughCodec() + { + var original = new Person(2, "harry") { - var original = new Person(2, "harry") - { - FavouriteColor = "redborine", - StarSign = "Aquaricorn" - }; + FavouriteColor = "redborine", + StarSign = "Aquaricorn" + }; - var result = RoundTripThroughCodec(original); + var result = RoundTripThroughCodec(original); - Assert.Equal(original.Age, result.Age); - Assert.Equal(original.Name, result.Name); - Assert.Equal(original.FavouriteColor, result.FavouriteColor); - Assert.Equal(original.StarSign, result.StarSign); - } + Assert.Equal(original.Age, result.Age); + Assert.Equal(original.Name, result.Name); + Assert.Equal(original.FavouriteColor, result.FavouriteColor); + Assert.Equal(original.StarSign, result.StarSign); + } - [Fact] - public void RecursiveTypeSerializersRoundTripThroughSerializer() - { - var original = new RecursiveClass { IntProperty = 30 }; - original.RecursiveProperty = original; - var result = (RecursiveClass)RoundTripThroughUntypedSerializer(original, out _); + [Fact] + public void RecursiveTypeSerializersRoundTripThroughSerializer() + { + var original = new RecursiveClass { IntProperty = 30 }; + original.RecursiveProperty = original; + var result = (RecursiveClass)RoundTripThroughUntypedSerializer(original, out _); - Assert.NotNull(result.RecursiveProperty); - Assert.Same(result, result.RecursiveProperty); - Assert.Equal(original.IntProperty, result.IntProperty); - } + Assert.NotNull(result.RecursiveProperty); + Assert.Same(result, result.RecursiveProperty); + Assert.Equal(original.IntProperty, result.IntProperty); + } - [Fact] - public void RecursiveTypeSerializersRoundTripThroughCodec() - { - var original = new RecursiveClass { IntProperty = 30 }; - original.RecursiveProperty = original; - var result = RoundTripThroughCodec(original); + [Fact] + public void RecursiveTypeSerializersRoundTripThroughCodec() + { + var original = new RecursiveClass { IntProperty = 30 }; + original.RecursiveProperty = original; + var result = RoundTripThroughCodec(original); - Assert.NotNull(result.RecursiveProperty); - Assert.Same(result, result.RecursiveProperty); - Assert.Equal(original.IntProperty, result.IntProperty); - } + Assert.NotNull(result.RecursiveProperty); + Assert.Same(result, result.RecursiveProperty); + Assert.Equal(original.IntProperty, result.IntProperty); + } - [Fact] - public void GeneratedRecordWithPCtorSerializersRoundTripThroughCodec() + [Fact] + public void GeneratedRecordWithPCtorSerializersRoundTripThroughCodec() + { + var original = new Person2(2, "harry") { - var original = new Person2(2, "harry") - { - FavouriteColor = "redborine", - StarSign = "Aquaricorn" - }; + FavouriteColor = "redborine", + StarSign = "Aquaricorn" + }; - var result = RoundTripThroughCodec(original); + var result = RoundTripThroughCodec(original); - Assert.Equal(original.Age, result.Age); - Assert.Equal(original.Name, result.Name); - Assert.Equal(original.FavouriteColor, result.FavouriteColor); - Assert.Equal(original.StarSign, result.StarSign); - } + Assert.Equal(original.Age, result.Age); + Assert.Equal(original.Name, result.Name); + Assert.Equal(original.FavouriteColor, result.FavouriteColor); + Assert.Equal(original.StarSign, result.StarSign); + } - [Fact] - public void GeneratedRecordWithExcludedPCtorSerializersRoundTripThroughCodec() + [Fact] + public void GeneratedRecordWithExcludedPCtorSerializersRoundTripThroughCodec() + { + var original = new Person3(2, "harry") { - var original = new Person3(2, "harry") - { - FavouriteColor = "redborine", - StarSign = "Aquaricorn" - }; + FavouriteColor = "redborine", + StarSign = "Aquaricorn" + }; - var result = RoundTripThroughCodec(original); + var result = RoundTripThroughCodec(original); - Assert.Equal(default, result.Age); - Assert.Equal(default, result.Name); - Assert.Equal(original.FavouriteColor, result.FavouriteColor); - Assert.Equal(original.StarSign, result.StarSign); - } + Assert.Equal(default, result.Age); + Assert.Equal(default, result.Name); + Assert.Equal(original.FavouriteColor, result.FavouriteColor); + Assert.Equal(original.StarSign, result.StarSign); + } - [Fact] - public void GeneratedRecordWithExclusiveCtorSerializersRoundTripThroughCodec() - { - var original = new Person4(2, "harry"); + [Fact] + public void GeneratedRecordWithExclusiveCtorSerializersRoundTripThroughCodec() + { + var original = new Person4(2, "harry"); - var result = RoundTripThroughCodec(original); + var result = RoundTripThroughCodec(original); - Assert.Equal(original.Age, result.Age); - Assert.Equal(original.Name, result.Name); - } + Assert.Equal(original.Age, result.Age); + Assert.Equal(original.Name, result.Name); + } - /// - /// Tests that a record type can be serialized bitwise identically to a regular (non-record) class with the same layout. - /// - [Fact] - public void RecordSerializedAsRegularClass() - { - var original = new Person5(2, "harry") { FavouriteColor = "redborine", StarSign = "Aquaricorn" }; + /// + /// Tests that a record type can be serialized bitwise identically to a regular (non-record) class with the same layout. + /// + [Fact] + public void RecordSerializedAsRegularClass() + { + var original = new Person5(2, "harry") { FavouriteColor = "redborine", StarSign = "Aquaricorn" }; - var result = RoundTripThroughCodec(original); + var result = RoundTripThroughCodec(original); - Assert.Equal(original.Age, result.Age); - Assert.Equal(original.Name, result.Name); - Assert.Equal(original.FavouriteColor, result.FavouriteColor); - Assert.Equal(original.StarSign, result.StarSign); + Assert.Equal(original.Age, result.Age); + Assert.Equal(original.Name, result.Name); + Assert.Equal(original.FavouriteColor, result.FavouriteColor); + Assert.Equal(original.StarSign, result.StarSign); - // Note that this only works because we are serializing each object using the "expected type" optimization and - // therefore omitting the concrete type names. - var originalAsArray = _serializer.SerializeToArray(original); - var classVersion = new Person5_Class { Age = 2, Name = "harry", FavouriteColor = "redborine", StarSign = "Aquaricorn" }; - var classAsArray = _serializer.SerializeToArray(classVersion); - Assert.Equal(originalAsArray, classAsArray); - } + // Note that this only works because we are serializing each object using the "expected type" optimization and + // therefore omitting the concrete type names. + var originalAsArray = _serializer.SerializeToArray(original); + var classVersion = new Person5_Class { Age = 2, Name = "harry", FavouriteColor = "redborine", StarSign = "Aquaricorn" }; + var classAsArray = _serializer.SerializeToArray(classVersion); + Assert.Equal(originalAsArray, classAsArray); + } - [Fact] - public void GeneratedSerializersRoundTripThroughSerializer() - { - var original = new SomeClassWithSerializers { IntField = 2, IntProperty = 30 }; - var result = (SomeClassWithSerializers)RoundTripThroughUntypedSerializer(original, out _); + [Fact] + public void GeneratedSerializersRoundTripThroughSerializer() + { + var original = new SomeClassWithSerializers { IntField = 2, IntProperty = 30 }; + var result = (SomeClassWithSerializers)RoundTripThroughUntypedSerializer(original, out _); - Assert.Equal(original.IntField, result.IntField); - Assert.Equal(original.IntProperty, result.IntProperty); - } + Assert.Equal(original.IntField, result.IntField); + Assert.Equal(original.IntProperty, result.IntProperty); + } - [Fact] - public void GeneratedSerializersRoundTripThroughSerializer_ImmutableClass() - { - var original = new ImmutableClass(30, 2, 88, 99); - var result = (ImmutableClass)RoundTripThroughUntypedSerializer(original, out _); + [Fact] + public void GeneratedSerializersRoundTripThroughSerializer_ImmutableClass() + { + var original = new ImmutableClass(30, 2, 88, 99); + var result = (ImmutableClass)RoundTripThroughUntypedSerializer(original, out _); - Assert.Equal(original.GetIntField(), result.GetIntField()); - Assert.Equal(original.IntProperty, result.IntProperty); - Assert.Equal(0, result.UnmarkedField); - Assert.Equal(0, result.UnmarkedProperty); - } + Assert.Equal(original.GetIntField(), result.GetIntField()); + Assert.Equal(original.IntProperty, result.IntProperty); + Assert.Equal(0, result.UnmarkedField); + Assert.Equal(0, result.UnmarkedProperty); + } - [Fact] - public void GeneratedSerializersRoundTripThroughSerializer_ImmutableStruct() - { - var original = new ImmutableStruct(30, 2); - var result = (ImmutableStruct)RoundTripThroughUntypedSerializer(original, out _); + [Fact] + public void GeneratedSerializersRoundTripThroughSerializer_ImmutableStruct() + { + var original = new ImmutableStruct(30, 2); + var result = (ImmutableStruct)RoundTripThroughUntypedSerializer(original, out _); - Assert.Equal(original.GetIntField(), result.GetIntField()); - Assert.Equal(original.IntProperty, result.IntProperty); - } + Assert.Equal(original.GetIntField(), result.GetIntField()); + Assert.Equal(original.IntProperty, result.IntProperty); + } - [Fact] - public void UnmarkedFieldsAreNotSerialized() - { - var original = new SomeClassWithSerializers { IntField = 2, IntProperty = 30, UnmarkedField = 12, UnmarkedProperty = 47 }; - var result = RoundTripThroughCodec(original); + [Fact] + public void UnmarkedFieldsAreNotSerialized() + { + var original = new SomeClassWithSerializers { IntField = 2, IntProperty = 30, UnmarkedField = 12, UnmarkedProperty = 47 }; + var result = RoundTripThroughCodec(original); - Assert.NotEqual(original.UnmarkedField, result.UnmarkedField); - Assert.NotEqual(original.UnmarkedProperty, result.UnmarkedProperty); - } + Assert.NotEqual(original.UnmarkedField, result.UnmarkedField); + Assert.NotEqual(original.UnmarkedProperty, result.UnmarkedProperty); + } - [Fact] - public void GenericPocosCanRoundTrip() + [Fact] + public void GenericPocosCanRoundTrip() + { + var original = new GenericPoco { - var original = new GenericPoco - { - ArrayField = new[] { "a", "bb", "ccc" }, - Field = Guid.NewGuid().ToString("N") - }; - var result = (GenericPoco)RoundTripThroughUntypedSerializer(original, out var formattedBitStream); - - Assert.Equal(original.ArrayField, result.ArrayField); - Assert.Equal(original.Field, result.Field); - Assert.Contains("gpoco`1", formattedBitStream); - } + ArrayField = new[] { "a", "bb", "ccc" }, + Field = Guid.NewGuid().ToString("N") + }; + var result = (GenericPoco)RoundTripThroughUntypedSerializer(original, out var formattedBitStream); + + Assert.Equal(original.ArrayField, result.ArrayField); + Assert.Equal(original.Field, result.Field); + Assert.Contains("gpoco`1", formattedBitStream); + } - [Fact] - public void NestedGenericPocoWithTypeAlias() + [Fact] + public void NestedGenericPocoWithTypeAlias() + { + var original = new GenericPoco> { - var original = new GenericPoco> + Field = new GenericPoco { - Field = new GenericPoco - { - Field = Guid.NewGuid().ToString("N") - } - }; - - RoundTripThroughUntypedSerializer(original, out var formattedBitStream); - Assert.Contains("gpoco`1[[gpoco`1[[string]]]]", formattedBitStream); - } + Field = Guid.NewGuid().ToString("N") + } + }; - [Fact] - public void ArraysAreSupported() - { - var original = new[] { "a", "bb", "ccc" }; - var result = (string[])RoundTripThroughUntypedSerializer(original, out _); + RoundTripThroughUntypedSerializer(original, out var formattedBitStream); + Assert.Contains("gpoco`1[[gpoco`1[[string]]]]", formattedBitStream); + } - Assert.Equal(original, result); - } + [Fact] + public void ArraysAreSupported() + { + var original = new[] { "a", "bb", "ccc" }; + var result = (string[])RoundTripThroughUntypedSerializer(original, out _); - [Fact] - public void ArraysPocoRoundTrip() - { - var original = new ArrayPoco - { - Array = new[] { 1, 2, 3 }, - Dim2 = new int[,] { { 1 }, { 2 } }, - Dim3 = new int[,,] { { { 2 } } }, - Dim4 = new int[,,,] { { { { 4 } } } }, - Dim32 = new int[,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,] { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { 809 } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } }, - Jagged = new int[][] { new int[] { 909 } } - }; - var result = (ArrayPoco)RoundTripThroughUntypedSerializer(original, out _); - - Assert.Equal(JsonConvert.SerializeObject(original), JsonConvert.SerializeObject(result)); - } + Assert.Equal(original, result); + } - [Fact] - public void MultiDimensionalArraysAreSupported() + [Fact] + public void ArraysPocoRoundTrip() + { + var original = new ArrayPoco { - var array2d = new string[,] { { "1", "2", "3" }, { "4", "5", "6" }, { "7", "8", "9" } }; - var result2d = (string[,])RoundTripThroughUntypedSerializer(array2d, out _); - - Assert.Equal(array2d, result2d); - var array3d = new string[,,] - { - { { "b", "b", "4" }, { "a", "g", "a" }, { "a", "g", "p" } }, - { { "g", "r", "g" }, { "1", "3", "a" }, { "l", "k", "a" } }, - { { "z", "b", "g" }, { "5", "7", "a" }, { "5", "n", "0" } } - }; - var result3d = (string[,,])RoundTripThroughUntypedSerializer(array3d, out _); + Array = new[] { 1, 2, 3 }, + Dim2 = new int[,] { { 1 }, { 2 } }, + Dim3 = new int[,,] { { { 2 } } }, + Dim4 = new int[,,,] { { { { 4 } } } }, + Dim32 = new int[,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,] { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { 809 } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } }, + Jagged = new int[][] { new int[] { 909 } } + }; + var result = (ArrayPoco)RoundTripThroughUntypedSerializer(original, out _); + + Assert.Equal(JsonConvert.SerializeObject(original), JsonConvert.SerializeObject(result)); + } - Assert.Equal(array3d, result3d); - } + [Fact] + public void MultiDimensionalArraysAreSupported() + { + var array2d = new string[,] { { "1", "2", "3" }, { "4", "5", "6" }, { "7", "8", "9" } }; + var result2d = (string[,])RoundTripThroughUntypedSerializer(array2d, out _); - [Fact] - public void SystemCollectionsRoundTrip() + Assert.Equal(array2d, result2d); + var array3d = new string[,,] { - var concurrentQueueField = new ConcurrentQueue(); - concurrentQueueField.Enqueue(4); + { { "b", "b", "4" }, { "a", "g", "a" }, { "a", "g", "p" } }, + { { "g", "r", "g" }, { "1", "3", "a" }, { "l", "k", "a" } }, + { { "z", "b", "g" }, { "5", "7", "a" }, { "5", "n", "0" } } + }; + var result3d = (string[,,])RoundTripThroughUntypedSerializer(array3d, out _); - var concurrentQueueProperty = new ConcurrentQueue(); - concurrentQueueProperty.Enqueue(5); - concurrentQueueProperty.Enqueue(6); + Assert.Equal(array3d, result3d); + } - var concurrentDictField = new ConcurrentDictionary(); - _ = concurrentDictField.TryAdd("nine", 9); + [Fact] + public void SystemCollectionsRoundTrip() + { + var concurrentQueueField = new ConcurrentQueue(); + concurrentQueueField.Enqueue(4); - var concurrentDictProperty = new ConcurrentDictionary(); - _ = concurrentDictProperty.TryAdd("ten", 10); - _ = concurrentDictProperty.TryAdd("eleven", 11); + var concurrentQueueProperty = new ConcurrentQueue(); + concurrentQueueProperty.Enqueue(5); + concurrentQueueProperty.Enqueue(6); - var original = new SystemCollectionsClass - { - hashSetField = new HashSet { "one" }, - HashSetProperty = new HashSet { "two", "three" }, - concurrentQueueField = concurrentQueueField, - ConcurrentQueueProperty = concurrentQueueProperty, - concurrentDictField = concurrentDictField, - ConcurrentDictProperty = concurrentDictProperty - }; - var result = RoundTripThroughCodec(original); - - Assert.Equal(original.hashSetField, result.hashSetField); - Assert.Equal(original.HashSetProperty, result.HashSetProperty); - - Assert.Equal(original.concurrentQueueField, result.concurrentQueueField); - Assert.Equal(original.ConcurrentQueueProperty, result.ConcurrentQueueProperty); - - // Order of the key-value pairs in the return value may not match the order of the key-value pairs in the surrogate - Assert.Equal(original.concurrentDictField["nine"], result.concurrentDictField["nine"]); - Assert.Equal(original.ConcurrentDictProperty["ten"], result.ConcurrentDictProperty["ten"]); - Assert.Equal(original.ConcurrentDictProperty["eleven"], result.ConcurrentDictProperty["eleven"]); - } + var concurrentDictField = new ConcurrentDictionary(); + _ = concurrentDictField.TryAdd("nine", 9); - [Fact] - public void ClassWithLargeCollectionAndUriRoundTrip() - { - var largeCollection = new List(200); - for (int i = 0; i < 200; i++) - { - largeCollection.Add(i.ToString()); - } + var concurrentDictProperty = new ConcurrentDictionary(); + _ = concurrentDictProperty.TryAdd("ten", 10); + _ = concurrentDictProperty.TryAdd("eleven", 11); - var original = new ClassWithLargeCollectionAndUri - { - LargeCollection = largeCollection, - Uri = new($"http://www.{Guid.NewGuid()}.com/") - }; + var original = new SystemCollectionsClass + { + hashSetField = new HashSet { "one" }, + HashSetProperty = new HashSet { "two", "three" }, + concurrentQueueField = concurrentQueueField, + ConcurrentQueueProperty = concurrentQueueProperty, + concurrentDictField = concurrentDictField, + ConcurrentDictProperty = concurrentDictProperty + }; + var result = RoundTripThroughCodec(original); + + Assert.Equal(original.hashSetField, result.hashSetField); + Assert.Equal(original.HashSetProperty, result.HashSetProperty); + + Assert.Equal(original.concurrentQueueField, result.concurrentQueueField); + Assert.Equal(original.ConcurrentQueueProperty, result.ConcurrentQueueProperty); + + // Order of the key-value pairs in the return value may not match the order of the key-value pairs in the surrogate + Assert.Equal(original.concurrentDictField["nine"], result.concurrentDictField["nine"]); + Assert.Equal(original.ConcurrentDictProperty["ten"], result.ConcurrentDictProperty["ten"]); + Assert.Equal(original.ConcurrentDictProperty["eleven"], result.ConcurrentDictProperty["eleven"]); + } - var result = RoundTripThroughCodec(original); - Assert.Equal(original.Uri, result.Uri); + [Fact] + public void ClassWithLargeCollectionAndUriRoundTrip() + { + var largeCollection = new List(200); + for (int i = 0; i < 200; i++) + { + largeCollection.Add(i.ToString()); } - [Fact] - public void ClassWithManualSerializablePropertyRoundTrip() + var original = new ClassWithLargeCollectionAndUri { - var original = new ClassWithManualSerializableProperty - { - GuidProperty = Guid.NewGuid(), - }; + LargeCollection = largeCollection, + Uri = new($"http://www.{Guid.NewGuid()}.com/") + }; - var result = RoundTripThroughCodec(original); - Assert.Equal(original.GuidProperty, result.GuidProperty); - Assert.Equal(original.StringProperty, result.StringProperty); + var result = RoundTripThroughCodec(original); + Assert.Equal(original.Uri, result.Uri); + } - var guidValue = Guid.NewGuid(); - original.StringProperty = guidValue.ToString("N"); - result = RoundTripThroughCodec(original); + [Fact] + public void ClassWithManualSerializablePropertyRoundTrip() + { + var original = new ClassWithManualSerializableProperty + { + GuidProperty = Guid.NewGuid(), + }; - Assert.Equal(guidValue, result.GuidProperty); - Assert.Equal(original.GuidProperty, result.GuidProperty); + var result = RoundTripThroughCodec(original); + Assert.Equal(original.GuidProperty, result.GuidProperty); + Assert.Equal(original.StringProperty, result.StringProperty); - Assert.Equal(guidValue.ToString("N"), result.StringProperty); - Assert.Equal(original.StringProperty, result.StringProperty); + var guidValue = Guid.NewGuid(); + original.StringProperty = guidValue.ToString("N"); + result = RoundTripThroughCodec(original); - original.StringProperty = "bananas"; - result = RoundTripThroughCodec(original); - - Assert.Equal(default(Guid), result.GuidProperty); - Assert.Equal(original.GuidProperty, result.GuidProperty); - Assert.Equal("bananas", result.StringProperty); - } + Assert.Equal(guidValue, result.GuidProperty); + Assert.Equal(original.GuidProperty, result.GuidProperty); - [Fact] - public void ImmutableClassWithImplicitFieldIdsRoundTrip() - { - var original = new ClassWithImplicitFieldIds("apples", MyCustomEnum.One); - var result = RoundTripThroughCodec(original); + Assert.Equal(guidValue.ToString("N"), result.StringProperty); + Assert.Equal(original.StringProperty, result.StringProperty); - Assert.Equal(original.StringValue, result.StringValue); - Assert.Equal(original.EnumValue, result.EnumValue); - } + original.StringProperty = "bananas"; + result = RoundTripThroughCodec(original); - [Fact] - public void CopyImmutableAndSealedTypes() - { - DoTest(new MyImmutableSub { BaseValue = 1, SubValue = 2 }); - DoTest(new MyMutableSub { BaseValue = 1, SubValue = 2 }); - DoTest(new MySealedSub { BaseValue = 1, SubValue = 2 }); - DoTest(new MySealedImmutableSub { BaseValue = 1, SubValue = 2 }); - DoTest(new MyUnsealedImmutableSub { BaseValue = 1, SubValue = 2 }); + Assert.Equal(default(Guid), result.GuidProperty); + Assert.Equal(original.GuidProperty, result.GuidProperty); + Assert.Equal("bananas", result.StringProperty); + } - void DoTest(T original) where T : TBase, IMySub - { - var res1 = Copy(original); - Assert.Equal(original.BaseValue, res1.BaseValue); - Assert.Equal(original.SubValue, res1.SubValue); + [Fact] + public void ImmutableClassWithImplicitFieldIdsRoundTrip() + { + var original = new ClassWithImplicitFieldIds("apples", MyCustomEnum.One); + var result = RoundTripThroughCodec(original); - var res2 = Assert.IsType(Copy(original)); - Assert.Equal(original.BaseValue, res2.BaseValue); - Assert.Equal(original.SubValue, res2.SubValue); + Assert.Equal(original.StringValue, result.StringValue); + Assert.Equal(original.EnumValue, result.EnumValue); + } - var res3 = Assert.IsType(Copy(original)); - Assert.Equal(original.BaseValue, res3.BaseValue); - Assert.Equal(original.SubValue, res3.SubValue); - } - } + [Fact] + public void CopyImmutableAndSealedTypes() + { + DoTest(new MyImmutableSub { BaseValue = 1, SubValue = 2 }); + DoTest(new MyMutableSub { BaseValue = 1, SubValue = 2 }); + DoTest(new MySealedSub { BaseValue = 1, SubValue = 2 }); + DoTest(new MySealedImmutableSub { BaseValue = 1, SubValue = 2 }); + DoTest(new MyUnsealedImmutableSub { BaseValue = 1, SubValue = 2 }); - [Fact] - public void TypeReferencesAreEncodedOnce() + void DoTest(T original) where T : TBase, IMySub { - var original = new object[] { new MyValue(1), new MyValue(2), new MyValue(3) }; - var result = (object[])RoundTripThroughUntypedSerializer(original, out var formattedBitStream); + var res1 = Copy(original); + Assert.Equal(original.BaseValue, res1.BaseValue); + Assert.Equal(original.SubValue, res1.SubValue); + + var res2 = Assert.IsType(Copy(original)); + Assert.Equal(original.BaseValue, res2.BaseValue); + Assert.Equal(original.SubValue, res2.SubValue); - Assert.Equal(original, result); - Assert.Contains("SchemaType: Referenced RuntimeType: MyValue", formattedBitStream); + var res3 = Assert.IsType(Copy(original)); + Assert.Equal(original.BaseValue, res3.BaseValue); + Assert.Equal(original.SubValue, res3.SubValue); } + } - [Fact] - public void TypeCodecDoesNotUpdateTypeReferences() - { - var original = new ClassWithTypeFields { Type1 = typeof(MyValue), UntypedValue = new MyValue(42), Type2 = typeof(MyValue) }; - var result = (ClassWithTypeFields)RoundTripThroughUntypedSerializer(original, out var formattedBitStream); + [Fact] + public void TypeReferencesAreEncodedOnce() + { + var original = new object[] { new MyValue(1), new MyValue(2), new MyValue(3) }; + var result = (object[])RoundTripThroughUntypedSerializer(original, out var formattedBitStream); - Assert.Equal(original.Type1, result.Type1); - Assert.Equal(original.UntypedValue, result.UntypedValue); - Assert.Equal(original.Type2, result.Type2); + Assert.Equal(original, result); + Assert.Contains("SchemaType: Referenced RuntimeType: MyValue", formattedBitStream); + } - Assert.Contains("[#4 LengthPrefixed Id: 1 SchemaType: Expected]", formattedBitStream); // Type1 - Assert.Contains("[#5 TagDelimited Id: 2 SchemaType: Encoded RuntimeType: MyValue", formattedBitStream); // UntypedValue - Assert.Contains("[#7 Reference Id: 3 SchemaType: Expected", formattedBitStream); // Type2 - } + [Fact] + public void TypeCodecDoesNotUpdateTypeReferences() + { + var original = new ClassWithTypeFields { Type1 = typeof(MyValue), UntypedValue = new MyValue(42), Type2 = typeof(MyValue) }; + var result = (ClassWithTypeFields)RoundTripThroughUntypedSerializer(original, out var formattedBitStream); - [Fact] - public void TypeCodecConsumesTypeReferences() - { - var original = new ClassWithTypeFields { UntypedValue = new MyValue(42), Type2 = typeof(MyValue) }; - var result = (ClassWithTypeFields)RoundTripThroughUntypedSerializer(original, out var formattedBitStream); + Assert.Equal(original.Type1, result.Type1); + Assert.Equal(original.UntypedValue, result.UntypedValue); + Assert.Equal(original.Type2, result.Type2); - Assert.Equal(original.Type1, result.Type1); - Assert.Equal(original.UntypedValue, result.UntypedValue); - Assert.Equal(original.Type2, result.Type2); + Assert.Contains("[#4 LengthPrefixed Id: 1 SchemaType: Expected]", formattedBitStream); // Type1 + Assert.Contains("[#5 TagDelimited Id: 2 SchemaType: Encoded RuntimeType: MyValue", formattedBitStream); // UntypedValue + Assert.Contains("[#7 Reference Id: 3 SchemaType: Expected", formattedBitStream); // Type2 + } - Assert.Contains("[#2 Reference Id: 1 SchemaType: Expected Reference: 0]", formattedBitStream); // Type1 - Assert.Contains("[#3 TagDelimited Id: 2 SchemaType: Encoded RuntimeType: MyValue", formattedBitStream); // UntypedValue - Assert.Contains("[#5 TagDelimited Id: 3 SchemaType: Expected]", formattedBitStream); // Type2 - Assert.Contains("[#7 VarInt Id: 2 SchemaType: Expected] Value: 2", formattedBitStream); // type reference from Type2 field pointing to the encoded field type of UntypedValue - } + [Fact] + public void TypeCodecConsumesTypeReferences() + { + var original = new ClassWithTypeFields { UntypedValue = new MyValue(42), Type2 = typeof(MyValue) }; + var result = (ClassWithTypeFields)RoundTripThroughUntypedSerializer(original, out var formattedBitStream); - public void Dispose() => _serviceProvider?.Dispose(); + Assert.Equal(original.Type1, result.Type1); + Assert.Equal(original.UntypedValue, result.UntypedValue); + Assert.Equal(original.Type2, result.Type2); - private T RoundTripThroughCodec(T original) + Assert.Contains("[#2 Reference Id: 1 SchemaType: Expected Reference: 0]", formattedBitStream); // Type1 + Assert.Contains("[#3 TagDelimited Id: 2 SchemaType: Encoded RuntimeType: MyValue", formattedBitStream); // UntypedValue + Assert.Contains("[#5 TagDelimited Id: 3 SchemaType: Expected]", formattedBitStream); // Type2 + Assert.Contains("[#7 VarInt Id: 2 SchemaType: Expected] Value: 2", formattedBitStream); // type reference from Type2 field pointing to the encoded field type of UntypedValue + } + + [Fact] + public void TypeDerivedFromDictionary() + { + var original = new DerivedFromDictionary(StringComparer.OrdinalIgnoreCase) { - T result; - var pipe = new Pipe(); - using (var readerSession = _sessionPool.GetSession()) - using (var writeSession = _sessionPool.GetSession()) - { - var writer = Writer.Create(pipe.Writer, writeSession); - var codec = _codecProvider.GetCodec(); - codec.WriteField( - ref writer, - 0, - null, - original); - writer.Commit(); - _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); - pipe.Writer.Complete(); - - _ = pipe.Reader.TryRead(out var readResult); - var reader = Reader.Create(readResult.Buffer, readerSession); - - var previousPos = reader.Position; - var initialHeader = reader.ReadFieldHeader(); - Assert.True(reader.Position > previousPos); - - result = codec.ReadValue(ref reader, initialHeader); - pipe.Reader.AdvanceTo(readResult.Buffer.End); - pipe.Reader.Complete(); - } + { "a", 1 }, + { "b", 2 }, + }; + original.IntProperty = Guid.NewGuid().GetHashCode(); - return result; - } + var result = RoundTripThroughCodec(original); + Assert.Equal(original.Count, result.Count); + Assert.Equal(original.IntProperty, result.IntProperty); + + // Set a duplicate key, differing by case, to check that the equality comparer has been set correctly. + result["A"] = 1; + result["a"] = 1; + + Assert.True(original.SequenceEqual(result)); + } - private T Copy(T original) + public void Dispose() => _serviceProvider?.Dispose(); + + private T RoundTripThroughCodec(T original) + { + T result; + var pipe = new Pipe(); + using (var readerSession = _sessionPool.GetSession()) + using (var writeSession = _sessionPool.GetSession()) { - var copier = _serviceProvider.GetRequiredService>(); - return copier.Copy(original); + var writer = Writer.Create(pipe.Writer, writeSession); + var codec = _codecProvider.GetCodec(); + codec.WriteField( + ref writer, + 0, + null, + original); + writer.Commit(); + _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); + pipe.Writer.Complete(); + + _ = pipe.Reader.TryRead(out var readResult); + var reader = Reader.Create(readResult.Buffer, readerSession); + + var previousPos = reader.Position; + var initialHeader = reader.ReadFieldHeader(); + Assert.True(reader.Position > previousPos); + + result = codec.ReadValue(ref reader, initialHeader); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + pipe.Reader.Complete(); } - private object RoundTripThroughUntypedSerializer(object original, out string formattedBitStream) - { - var pipe = new Pipe(); - object result; - using (var readerSession = _sessionPool.GetSession()) - using (var writeSession = _sessionPool.GetSession()) - { - var writer = Writer.Create(pipe.Writer, writeSession); - var serializer = _serviceProvider.GetService>(); - serializer.Serialize(original, ref writer); + return result; + } + + private T Copy(T original) + { + var copier = _serviceProvider.GetRequiredService>(); + return copier.Copy(original); + } - _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); - pipe.Writer.Complete(); + private object RoundTripThroughUntypedSerializer(object original, out string formattedBitStream) + { + var pipe = new Pipe(); + object result; + using (var readerSession = _sessionPool.GetSession()) + using (var writeSession = _sessionPool.GetSession()) + { + var writer = Writer.Create(pipe.Writer, writeSession); + var serializer = _serviceProvider.GetService>(); + serializer.Serialize(original, ref writer); - _ = pipe.Reader.TryRead(out var readResult); + _ = pipe.Writer.FlushAsync().AsTask().GetAwaiter().GetResult(); + pipe.Writer.Complete(); - using var analyzerSession = _sessionPool.GetSession(); - formattedBitStream = BitStreamFormatter.Format(readResult.Buffer, analyzerSession); + _ = pipe.Reader.TryRead(out var readResult); - var reader = Reader.Create(readResult.Buffer, readerSession); + using var analyzerSession = _sessionPool.GetSession(); + formattedBitStream = BitStreamFormatter.Format(readResult.Buffer, analyzerSession); - result = serializer.Deserialize(ref reader); - pipe.Reader.AdvanceTo(readResult.Buffer.End); - pipe.Reader.Complete(); - } + var reader = Reader.Create(readResult.Buffer, readerSession); - return result; + result = serializer.Deserialize(ref reader); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + pipe.Reader.Complete(); } + + return result; } } \ No newline at end of file diff --git a/test/Orleans.Serialization.UnitTests/Models.cs b/test/Orleans.Serialization.UnitTests/Models.cs index 77540457dd..7a6e0f3847 100644 --- a/test/Orleans.Serialization.UnitTests/Models.cs +++ b/test/Orleans.Serialization.UnitTests/Models.cs @@ -486,6 +486,17 @@ public class SerializableClassWithCompiledBase : List public int IntProperty { get; set; } } + [GenerateSerializer] + public sealed class DerivedFromDictionary : Dictionary + { + public DerivedFromDictionary(IEqualityComparer comparer) : base(comparer) + { + } + + [Id(0)] + public int IntProperty { get; set; } + } + [GenerateSerializer] [Alias("gpoco`1")] public class GenericPoco