diff --git a/src/Hyperion.Tests/Bugs.cs b/src/Hyperion.Tests/Bugs.cs index a4ebe36c..ebd9f562 100644 --- a/src/Hyperion.Tests/Bugs.cs +++ b/src/Hyperion.Tests/Bugs.cs @@ -8,21 +8,31 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using FluentAssertions; using Hyperion.Extensions; using Xunit; +using Xunit.Abstractions; namespace Hyperion.Tests { public class Bugs { + private readonly ITestOutputHelper _output; + + public Bugs(ITestOutputHelper output) + { + _output = output; + } + #region issue 58 public enum TrustLevel { Unknown, Suspicious, Partial, Fully } @@ -246,5 +256,224 @@ public Recover(SnapshotSelectionCriteria fromSnapshot, long toSequenceNr = long. /// public long ReplayMax { get; private set; } } + + delegate int TestDelegate(int x, int y); + + class Temp : IEquatable + { + public object[] SubArray { get; set; } + public int[] IntArray { get; set; } + public int[,] IntIntArray { get; set; } + public Poco Poco { get; set; } + public string String { get; set; } + public Dictionary Dictionary { get; set; } + public TestDelegate Delegate { get; set; } + public IEnumerable TestEnum { get; set; } + public Exception Exception { get; set; } + public ImmutableList ImmutableList { get; set; } + public ImmutableDictionary ImmutableDictionary { get; set; } + + public bool Equals(Temp other) + { + if (other == null) + throw new Exception("Equals failed."); + if (ReferenceEquals(this, other)) + throw new Exception("Equals failed."); + if (IntIntArray.Rank != other.IntIntArray.Rank) + throw new Exception("Equals failed."); + + for (var i = 0; i < IntIntArray.Rank; ++i) + { + for (var j = 0; j < IntIntArray.GetLength(i); ++j) + { + if (IntIntArray[j, i] != other.IntIntArray[j, i]) + throw new Exception("Equals failed."); + } + } + + if (Exception.GetType() != other.Exception.GetType()) + throw new Exception("Equals failed."); + if (Exception.Message != other.Exception.Message) + throw new Exception("Equals failed."); + if(Exception.InnerException != null + && Exception.InnerException.GetType() != other.Exception.InnerException.GetType()) + throw new Exception("Equals failed."); + + for (var i = 0; i < SubArray.Length; i++) + { + if (SubArray[i].GetType() != other.SubArray[i].GetType()) + throw new Exception("Equals failed."); + + if (SubArray[i] is Array arr) + { + var oArr = (Array)other.SubArray[i]; + for (var j = 0; j < arr.Length; ++j) + { + if (!arr.GetValue(j).Equals(oArr.GetValue(j))) + throw new Exception("Equals failed."); + } + } else if (!SubArray[i].Equals(other.SubArray[i])) + throw new Exception("Equals failed."); + } + + foreach (var key in Dictionary.Keys) + { + if (!Dictionary[key].Equals(other.Dictionary[key])) + throw new Exception("Equals failed."); + } + + foreach (var key in ImmutableDictionary.Keys) + { + if (!ImmutableDictionary[key].Equals(other.ImmutableDictionary[key])) + throw new Exception("Equals failed."); + } + + if (other.Delegate(2, 2) != 4) + throw new Exception("Equals failed."); + + if(!IntArray.SequenceEqual(other.IntArray)) + throw new Exception("Equals failed."); + if(!Equals(Poco, other.Poco)) + throw new Exception("Equals failed."); + if (String != other.String) + throw new Exception("Equals failed."); + if(!TestEnum.SequenceEqual(other.TestEnum)) + throw new Exception("Equals failed."); + if(!ImmutableList.SequenceEqual(other.ImmutableList)) + throw new Exception("Equals failed."); + + return true; + } + + public override bool Equals(object obj) + { + if (obj == null) throw new Exception("Equals failed."); + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) throw new Exception("Equals failed."); + return Equals((Temp) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (SubArray != null ? SubArray.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (IntArray != null ? IntArray.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (IntIntArray != null ? IntIntArray.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Poco != null ? Poco.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (String != null ? String.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Dictionary != null ? Dictionary.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Delegate != null ? Delegate.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (TestEnum != null ? TestEnum.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Exception != null ? Exception.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (ImmutableList != null ? ImmutableList.GetHashCode() : 0); + return hashCode; + } + } + } + + class Poco : IEquatable + { + public Poco() + { } + + public Poco(int intValue, string stringValue) + { + Int = intValue; + String = stringValue; + } + + public int Int { get; set; } + public string String { get; set; } + + public bool Equals(Poco other) + { + if (ReferenceEquals(null, other)) + throw new Exception("Equals failed."); + if (ReferenceEquals(this, other)) + throw new Exception("Equals failed."); + if(Int != other.Int) + throw new Exception("Equals failed."); + if(String != other.String) + throw new Exception("Equals failed."); + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) throw new Exception("Equals failed."); + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) throw new Exception("Equals failed."); + return Equals((Poco) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Int * 397) ^ (String != null ? String.GetHashCode() : 0); + } + } + } + + [Fact] + public void WritesManifestEvenIfKnown() + { + var stream = new MemoryStream(); + var msg = new Temp + { + SubArray = new object[] { 1, (byte)2, new object[] { 3 } }, + IntArray = new [] {1, 2, 3, 4, 5}, + IntIntArray = new [,] {{1, 2}, {3,4}, {5,6}, {7,8}}, + Poco = new Poco(999, "666"), + String = "huhu", + Dictionary = new Dictionary + { + { 666, "b" }, + { 999, "testString" }, + { 42, "iMaGiNe" } + }, + Delegate = (x, y) => x * y, + TestEnum = new[]{4,8,9,3,2}, + Exception = new ArgumentException("Test Exception", new IndexOutOfRangeException("-999")), + ImmutableList = new [] {9, 4, 6, 2, 5}.ToImmutableList(), + ImmutableDictionary = new Dictionary + { + { 666, "b" }, + { 999, "testString" }, + { 42, "iMaGiNe" } + }.ToImmutableDictionary(), + }; + var serializer = new Serializer(new SerializerOptions(knownTypes: new[] + { + typeof(object[]), + typeof(int[]), + typeof(int[,]), + typeof(Dictionary), + typeof(DictionaryEntry), + typeof(KeyValuePair), + typeof(Temp), + typeof(TestDelegate), + typeof(Enumerable), + typeof(IEnumerable), + typeof(Exception), + typeof(ArgumentException), + typeof(IndexOutOfRangeException), + typeof(FieldInfo), + typeof(ImmutableList), + typeof(ImmutableList), + typeof(ImmutableDictionary), + typeof(MethodInfo), + typeof(PropertyInfo), + })); + serializer.Serialize(msg, stream); + stream.Position = 0; + var a = stream.ToArray(); + var text = string.Join("", a.Select(x => x < 32 || x > 126 ? "" : ((char)x).ToString())); + _output.WriteLine(text); + var res = (Temp)serializer.Deserialize(stream); + Assert.DoesNotContain("System.Collections.Generic.Dictionary", text); + Assert.Equal(msg, res); + } } } diff --git a/src/Hyperion/SerializerFactories/ArraySerializerFactory.cs b/src/Hyperion/SerializerFactories/ArraySerializerFactory.cs index a4ac76a3..797d635b 100644 --- a/src/Hyperion/SerializerFactories/ArraySerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/ArraySerializerFactory.cs @@ -71,7 +71,14 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type WriteValues((dynamic)arr, stream, elementType, elementSerializer, session); }; arraySerializer.Initialize(reader, writer); - typeMapping.TryAdd(type, arraySerializer); + + if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index)) + { + var wrapper = new KnownTypeObjectSerializer(arraySerializer, index); + typeMapping.TryAdd(type, wrapper); + } + else + typeMapping.TryAdd(type, arraySerializer); return arraySerializer; } } diff --git a/src/Hyperion/SerializerFactories/DefaultDictionarySerializerFactory.cs b/src/Hyperion/SerializerFactories/DefaultDictionarySerializerFactory.cs index b7d26ece..29902e95 100644 --- a/src/Hyperion/SerializerFactories/DefaultDictionarySerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/DefaultDictionarySerializerFactory.cs @@ -31,7 +31,13 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type ConcurrentDictionary typeMapping) { var ser = new ObjectSerializer(type); - typeMapping.TryAdd(type, ser); + if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index)) + { + var wrapper = new KnownTypeObjectSerializer(ser, index); + typeMapping.TryAdd(type, wrapper); + } + else + typeMapping.TryAdd(type, ser); var elementSerializer = serializer.GetSerializerByType(typeof (DictionaryEntry)); var preserveObjectReferences = serializer.Options.PreserveObjectReferences; ObjectReader reader = (stream, session) => diff --git a/src/Hyperion/SerializerFactories/DelegateSerializerFactory.cs b/src/Hyperion/SerializerFactories/DelegateSerializerFactory.cs index 850233df..6181c81e 100644 --- a/src/Hyperion/SerializerFactories/DelegateSerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/DelegateSerializerFactory.cs @@ -31,7 +31,13 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type ConcurrentDictionary typeMapping) { var os = new ObjectSerializer(type); - typeMapping.TryAdd(type, os); + if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index)) + { + var wrapper = new KnownTypeObjectSerializer(os, index); + typeMapping.TryAdd(type, wrapper); + } + else + typeMapping.TryAdd(type, os); var methodInfoSerializer = serializer.GetSerializerByType(typeof(MethodInfo)); var preserveObjectReferences = serializer.Options.PreserveObjectReferences; ObjectReader reader = (stream, session) => diff --git a/src/Hyperion/SerializerFactories/ExceptionSerializerFactory.cs b/src/Hyperion/SerializerFactories/ExceptionSerializerFactory.cs index f7ac7b63..aca00d58 100644 --- a/src/Hyperion/SerializerFactories/ExceptionSerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/ExceptionSerializerFactory.cs @@ -91,7 +91,13 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type StringSerializer.WriteValueImpl(stream, stackTraceString, session); stream.WriteObjectWithManifest(innerException, session); }); - typeMapping.TryAdd(type, exceptionSerializer); + if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index)) + { + var wrapper = new KnownTypeObjectSerializer(exceptionSerializer, index); + typeMapping.TryAdd(type, wrapper); + } + else + typeMapping.TryAdd(type, exceptionSerializer); return exceptionSerializer; } } diff --git a/src/Hyperion/SerializerFactories/ImmutableCollectionsSerializerFactory.cs b/src/Hyperion/SerializerFactories/ImmutableCollectionsSerializerFactory.cs index e6eab05e..29004b9e 100644 --- a/src/Hyperion/SerializerFactories/ImmutableCollectionsSerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/ImmutableCollectionsSerializerFactory.cs @@ -49,7 +49,13 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type ConcurrentDictionary typeMapping) { var x = new ObjectSerializer(type); - typeMapping.TryAdd(type, x); + if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index)) + { + var wrapper = new KnownTypeObjectSerializer(x, index); + typeMapping.TryAdd(type, wrapper); + } + else + typeMapping.TryAdd(type, x); var preserveObjectReferences = serializer.Options.PreserveObjectReferences; var elementType = GetEnumerableType(type) ?? typeof (object); diff --git a/src/Hyperion/SerializerFactories/MultipleDimensionalArraySerialzierFactory.cs b/src/Hyperion/SerializerFactories/MultipleDimensionalArraySerialzierFactory.cs index cdfab7a2..86d83bce 100644 --- a/src/Hyperion/SerializerFactories/MultipleDimensionalArraySerialzierFactory.cs +++ b/src/Hyperion/SerializerFactories/MultipleDimensionalArraySerialzierFactory.cs @@ -142,7 +142,13 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type WriteValues((Array)arr, stream, elementType, elementSerializer, session); }; arraySerializer.Initialize(reader, writer); - typeMapping.TryAdd(type, arraySerializer); + if (serializer.Options.KnownTypesDict.TryGetValue(type, out var index)) + { + var wrapper = new KnownTypeObjectSerializer(arraySerializer, index); + typeMapping.TryAdd(type, wrapper); + } + else + typeMapping.TryAdd(type, arraySerializer); return arraySerializer; }