From 3a7237dd40d25daf27d48e6c52a707de50d7bb41 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 7 Oct 2021 06:53:59 +0700 Subject: [PATCH 1/2] Add spec --- .../IntegrationSpec.cs | 27 +++++++ src/Hyperion.Tests/CollectionTests.cs | 80 +++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs b/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs index 7ec81933..aaf7ece9 100644 --- a/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs +++ b/src/Hyperion.Akka.Integration.Tests/IntegrationSpec.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Akka.Actor; using Akka.Configuration; using Akka.Serialization; @@ -60,6 +61,32 @@ public void Akka_HyperionSerializer_should_serialize_properly() deserialized.Count.Should().Be(24); } + [Fact] + public void Bugfix263_Akka_HyperionSerializer_should_serialize_ActorPath_list() + { + var actor = Sys.ActorOf(Props.Create()); + var container = new ContainerClass(new List{ actor.Path, actor.Path }); + var serialized = _serializer.ToBinary(container); + var deserialized = _serializer.FromBinary(serialized); + deserialized.Destinations.Count.Should().Be(2); + deserialized.Destinations[0].Should().Be(deserialized.Destinations[1]); + } + + private class MyActor: ReceiveActor + { + + } + + private class ContainerClass + { + public ContainerClass(List destinations) + { + Destinations = destinations; + } + + public List Destinations { get; } + } + private class MyPocoClass { public string Name { get; set; } diff --git a/src/Hyperion.Tests/CollectionTests.cs b/src/Hyperion.Tests/CollectionTests.cs index 306028e0..91ecb9de 100644 --- a/src/Hyperion.Tests/CollectionTests.cs +++ b/src/Hyperion.Tests/CollectionTests.cs @@ -15,6 +15,7 @@ using System.Dynamic; using System.IO; using System.Linq; +using FluentAssertions; using Xunit; using Hyperion.SerializerFactories; using Hyperion.ValueSerializers; @@ -415,6 +416,35 @@ public void Issue18() Assert.True(msg.SequenceEqual(deserialized)); } + [Fact] + public void Issue263_CanSerializeArrayOfSurrogate_WhenPreservingObjectReference() + { + var invoked = new List(); + var serializer = new Serializer(new SerializerOptions( + preserveObjectReferences: true, + surrogates: new [] + { + Surrogate.Create( + to => to.ToSurrogate(), + from => { + invoked.Add(from); + return from.FromSurrogate(); + }), + })); + + var objectRef = new SurrogatedClass(5); + var expected = new List { objectRef, objectRef }; + using (var stream = new MemoryStream()) + { + serializer.Serialize(expected, stream); + stream.Position = 0; + var deserialized = serializer.Deserialize>(stream); + deserialized.Count.Should().Be(2); + invoked.Count.Should().Be(1); + ReferenceEquals(deserialized[0], deserialized[1]).Should().BeTrue(); + } + } + #region test classes public class CustomAdd : IEnumerable @@ -451,6 +481,56 @@ public CustomAddRange(IImmutableList inner) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + public class SurrogatedClass + { + public class ClassSurrogate + { + public ClassSurrogate(string value) + { + Value = value; + } + + public string Value { get; } + + public SurrogatedClass FromSurrogate() + => new SurrogatedClass(int.Parse(Value)); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is ClassSurrogate s && s.Value == Value; + } + + public override int GetHashCode() + { + return (Value != null ? Value.GetHashCode() : 0); + } + } + + public SurrogatedClass(int value) + { + Value = value; + } + + public int Value { get; } + + public ClassSurrogate ToSurrogate() + => new ClassSurrogate(Value.ToString()); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is SurrogatedClass s && s.Value == Value; + } + + public override int GetHashCode() + { + return Value; + } + } + #endregion [Fact] From 9303ca6d13002b32007582dd54304415cca54bcd Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Thu, 7 Oct 2021 07:24:10 +0700 Subject: [PATCH 2/2] Add preserve object reference support to surrogate deserializer --- .../CoreApiSpec.ApproveApi.approved.txt | 1 + src/Hyperion/DeserializeSession.cs | 9 +++++++++ .../FromSurrogateSerializerFactory.cs | 2 +- src/Hyperion/ValueSerializers/FromSurrogateSerializer.cs | 9 ++++++++- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt b/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt index a55d7c8f..d5fe90b4 100644 --- a/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt +++ b/src/Hyperion.API.Tests/CoreApiSpec.ApproveApi.approved.txt @@ -36,6 +36,7 @@ namespace Hyperion public object GetDeserializedObject(int id) { } public System.Type GetTypeFromTypeId(int typeId) { } public Hyperion.TypeVersionInfo GetVersionInfo([Hyperion.Internal.NotNull] System.Type type) { } + public void ReplaceOrAddTrackedDeserializedObject([Hyperion.Internal.NotNull] object origin, [Hyperion.Internal.NotNull] object replacement) { } public void TrackDeserializedObject([Hyperion.Internal.NotNull] object obj) { } public void TrackDeserializedType([Hyperion.Internal.NotNull] System.Type type) { } public void TrackDeserializedTypeWithVersion([Hyperion.Internal.NotNull] System.Type type, [Hyperion.Internal.NotNull] Hyperion.TypeVersionInfo versionInfo) { } diff --git a/src/Hyperion/DeserializeSession.cs b/src/Hyperion/DeserializeSession.cs index ed7bcadc..8f175aa0 100644 --- a/src/Hyperion/DeserializeSession.cs +++ b/src/Hyperion/DeserializeSession.cs @@ -65,6 +65,15 @@ public object GetDeserializedObject(int id) return _objectById[id]; } + public void ReplaceOrAddTrackedDeserializedObject([NotNull] object origin, [NotNull] object replacement) + { + var index = _objectById.IndexOf(origin); + if (index == -1) + _objectById.Add(origin); + else + _objectById[index] = replacement; + } + public void TrackDeserializedType([NotNull]Type type) { if (_identifierToType == null) diff --git a/src/Hyperion/SerializerFactories/FromSurrogateSerializerFactory.cs b/src/Hyperion/SerializerFactories/FromSurrogateSerializerFactory.cs index 2409fc5b..1dfd0fe6 100644 --- a/src/Hyperion/SerializerFactories/FromSurrogateSerializerFactory.cs +++ b/src/Hyperion/SerializerFactories/FromSurrogateSerializerFactory.cs @@ -31,7 +31,7 @@ public override ValueSerializer BuildSerializer(Serializer serializer, Type type var surrogate = serializer.Options.Surrogates.FirstOrDefault(s => s.To.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())); var objectSerializer = new ObjectSerializer(type); // ReSharper disable once PossibleNullReferenceException - var fromSurrogateSerializer = new FromSurrogateSerializer(surrogate.FromSurrogate, objectSerializer); + var fromSurrogateSerializer = new FromSurrogateSerializer(surrogate.FromSurrogate, objectSerializer, serializer.Options.PreserveObjectReferences); typeMapping.TryAdd(type, fromSurrogateSerializer); diff --git a/src/Hyperion/ValueSerializers/FromSurrogateSerializer.cs b/src/Hyperion/ValueSerializers/FromSurrogateSerializer.cs index 8111cbda..023c98b9 100644 --- a/src/Hyperion/ValueSerializers/FromSurrogateSerializer.cs +++ b/src/Hyperion/ValueSerializers/FromSurrogateSerializer.cs @@ -16,11 +16,16 @@ internal sealed class FromSurrogateSerializer : ValueSerializer { private readonly ValueSerializer _surrogateSerializer; private readonly Func _translator; + private readonly bool _preserveObjectReferences; - public FromSurrogateSerializer(Func translator, ValueSerializer surrogateSerializer) + public FromSurrogateSerializer( + Func translator, + ValueSerializer surrogateSerializer, + bool preserveObjectReferences) { _translator = translator; _surrogateSerializer = surrogateSerializer; + _preserveObjectReferences = preserveObjectReferences; } public override void WriteManifest(Stream stream, SerializerSession session) @@ -37,6 +42,8 @@ public override object ReadValue(Stream stream, DeserializerSession session) { var surrogateValue = _surrogateSerializer.ReadValue(stream, session); var value = _translator(surrogateValue); + if(_preserveObjectReferences) + session.ReplaceOrAddTrackedDeserializedObject(surrogateValue, value); return value; }