// // Copyright (c) 2012-2021 Antmicro // // This file is licensed under the MIT License. // Full license text is available in the LICENSE file. using Antmicro.Migrant.Hooks; using NUnit.Framework; using System.IO; using System; using System.Threading; using System.Collections.Generic; namespace Antmicro.Migrant.Tests { [TestFixture(false, false, true)] [TestFixture(true, false, true)] [TestFixture(false, true, true)] [TestFixture(true, true, true)] [TestFixture(false, false, false)] [TestFixture(true, false, false)] [TestFixture(false, true, false)] [TestFixture(true, true, false)] public class HooksTests : BaseTestWithSettings { public HooksTests(bool useGeneratedSerializer, bool useGeneratedDeserializer, bool useTypeStamping) : base(useGeneratedSerializer, useGeneratedDeserializer, false, false, false, useTypeStamping, true) { } [Test] public void ShouldInvokeChainOfHooksInCorrectOrder() { ClassWithTheReference.Reset(); var array = new ClassWithTheReference[6]; for(var i = 0; i < array.Length; i++) { array[i] = new ClassWithTheReference(i); } array[0].refOne = array[1]; array[0].refTwo = array[2]; array[1].refOne = array[3]; array[1].refTwo = array[4]; array[2].refOne = array[1]; array[2].refTwo = array[5]; var copy = SerializerClone(array); Assert.AreEqual(1, copy[5].value); Assert.AreEqual(2, copy[4].value); Assert.AreEqual(3, copy[3].value); Assert.AreEqual(4, copy[2].value); Assert.AreEqual(5, copy[1].value); Assert.AreEqual(6, copy[0].value); } private class ClassWithTheReference { public ClassWithTheReference(int id) { this.id = id; } // we don't use array/list here to avoid additional objects public ClassWithTheReference refOne; public ClassWithTheReference refTwo; public int value; public int id; [PostDeserialization] private void PostDeserializationMethod() { value = ++counter; } public static void Reset() { counter = 0; } private static int counter = 0; } [Test] public void ShouldInvokePreSerialization() { var mock = new PreSerializationMock(); var copy = SerializerClone(mock); Assert.IsTrue(mock.Invoked); Assert.IsTrue(copy.Invoked); } [Test] public void ShouldInvokeDerivedPreSerialization() { var mock = new PreSerializationMockDerived(); var copy = SerializerClone(mock); Assert.IsTrue(mock.Invoked); Assert.IsTrue(mock.DerivedInvoked); Assert.IsTrue(copy.Invoked); Assert.IsTrue(copy.DerivedInvoked); } [Test] public void ShouldInvokePostSerialization() { var mock = new ImmediatePostSerializationMock(); var copy = SerializerClone(mock); Assert.IsTrue(mock.Invoked); Assert.IsFalse(copy.Invoked); } [Test] public void ShouldInvokeDerivedPostSerialization() { var mock = new ImmediatePostSerializationMockDerived(); var copy = SerializerClone(mock); Assert.IsTrue(mock.Invoked); Assert.IsTrue(mock.DerivedInvoked); Assert.IsFalse(copy.Invoked); Assert.IsFalse(copy.DerivedInvoked); } [Test] public void ShouldInvokeStaticPostSerialization() { var mock = new StaticImmediatePostSerializationMock(); SerializerClone(mock); Assert.IsTrue(StaticImmediatePostSerializationMock.Invoked); } [Test] public void ShouldInvokePostDeserialization() { var mock = new PostDeserializationMock(); var copy = SerializerClone(mock); Assert.IsFalse(mock.Invoked); Assert.IsTrue(copy.Invoked); } [Test] public void ShouldInvokeDerivedPostDeserialization() { var mock = new PostDeserializationMockDerived(); var copy = SerializerClone(mock); Assert.IsFalse(mock.Invoked); Assert.IsFalse(mock.DerivedInvoked); Assert.IsTrue(copy.Invoked); Assert.IsTrue(copy.DerivedInvoked); } [Test] public void ShouldInvokeGlobalHooks() { var memoryStream = new MemoryStream(); var serializer = new Serializer(GetSettings()); var preSerializationCounter = 0; var postSerializationCounter = 0; var postDeserializationCounter = 0; serializer.OnPreSerialization += x => preSerializationCounter++; serializer.OnPostSerialization += x => postSerializationCounter++; serializer.OnPostDeserialization += x => postDeserializationCounter++; serializer.Serialize(new [] { "One", "Two" }, memoryStream); Assert.AreEqual(3, preSerializationCounter); Assert.AreEqual(3, postSerializationCounter); Assert.AreEqual(0, postDeserializationCounter); memoryStream.Seek(0, SeekOrigin.Begin); serializer.Deserialize(memoryStream); Assert.AreEqual(3, postDeserializationCounter); } [Test] public void ShouldHaveDeserializedReferencedObjectsWhenHookIsInvoked() { var referencing = new ReferencingPostDeserializationMock(); SerializerClone(referencing); } [Test] public void ShouldInvokePostHookAfterBothObjectsInCyclicReferenceAreDeserialized() { var a = new CyclicReferenceMockA(); a.B = new CyclicReferenceMockB(); a.B.A = a; SerializerClone(a); } [Test] public void ShouldInvokeLatePostSerializationHookAfterImmediate() { var late = new LatePostSerializationMockA(); SerializerClone(late); } [Test] public void ShouldInvokeLatePostDeserializationHookAfterImmediate() { var late = new LatePostSerializationMockA(); SerializerClone(late); } [Test] public void ShouldInvokeImmediateHooksInCorrectOrder() { var forOrderTest = new ForOrderTestA { B = new ForOrderTestB() }; var copy = SerializerClone(forOrderTest); Assert.Less(forOrderTest.B.HookInvokedOn, forOrderTest.HookInvokedOn); Assert.Less(copy.B.HookInvokedOn, copy.HookInvokedOn); } [Test] public void ShouldInvokeLateHooksInCorrectOrder() { var forLateOrderTest = new ForLateOrderTestA { B = new ForLateOrderTestB() }; var copy = SerializerClone(forLateOrderTest); Assert.Less(forLateOrderTest.B.HookInvokedOn, forLateOrderTest.HookInvokedOn); Assert.Less(copy.B.HookInvokedOn, copy.HookInvokedOn); } [Test] public void ShouldInvokePostSerializationEvenIfExceptionWasThrownDuringSerializationEarly() { ShouldInvokePostDeserializationEvenIfExceptionWasThrownDuringSerialization(new PrePostSerializationMock()); } [Test] public void ShouldInvokePostSerializationEvenIfExceptionWasThrownDuringSerializationLate() { ShouldInvokePostDeserializationEvenIfExceptionWasThrownDuringSerialization(new LatePrePostSerializationMock()); } [Test] public void ShouldInvokeHooksOnDerivedClassesInCorrectOrder() { var obj = new DerivedFromClassB(); var copy = SerializerClone(obj); Assert.IsTrue(copy.FlagC); } [Test] public void ShouldProperlyExecuteHooksOnVirtualMethod() { var mockWithVirtual = new MockWithVirtualDerived(); var copy = SerializerClone(mockWithVirtual); Assert.AreEqual(1, mockWithVirtual.BasePreSerializationCounter); Assert.AreEqual(2, mockWithVirtual.DerivedPreSerializationCounter); Assert.AreEqual(3, mockWithVirtual.BasePostSerializationCounter); Assert.AreEqual(4, mockWithVirtual.DerivedPostSerializationCounter); Assert.AreEqual(3, copy.BasePostDeserializationCounter); Assert.AreEqual(4, copy.DerivedPostDeserializationCounter); } [Test] public void ShouldFailWithSurrogateAndLatePostDeserializationHook() { try { var lateHook = new LateDeserializationMockA(); PseudoClone(lateHook, serializer => serializer.ForSurrogate().SetObject(x => new object())); Assert.Fail("Serialization finished while it should fail."); } catch(InvalidOperationException) { } } private void ShouldInvokePostDeserializationEvenIfExceptionWasThrownDuringSerialization(T prePostMock) where T : IPrePostMock { try { SerializerClone(prePostMock); Assert.Fail("The exception has not propagated."); } catch(InvalidOperationException) { } Assert.AreEqual(true, prePostMock.PreExecuted); Assert.AreEqual(true, prePostMock.PostExecuted); } } public class PreSerializationMock { [PreSerialization] private void PreSerialization() { Invoked = true; } public bool Invoked { get; private set; } } public class PreSerializationMockDerived : PreSerializationMock { [PreSerialization] private void PreSerializationDerived() { DerivedInvoked = true; } public bool DerivedInvoked { get; private set; } } public class ImmediatePostSerializationMock { [PostSerializationAttribute] private void PostSerialization() { Invoked = true; } public bool Invoked { get; private set; } } public class ImmediatePostSerializationMockDerived : ImmediatePostSerializationMock { [PostSerializationAttribute] private void PostSerializationDerived() { DerivedInvoked = true; } public bool DerivedInvoked { get; private set; } } public class StaticImmediatePostSerializationMock { [PostSerializationAttribute] private static void PostSerialization() { Invoked = true; } public static bool Invoked { get; private set; } } public class PostDeserializationMock { [LatePostDeserializationAttribute] private void PostDeserialization() { Invoked = true; } public bool Invoked { get; private set; } } public class PostDeserializationMockDerived : PostDeserializationMock { [LatePostDeserializationAttribute] private void PostDeserializationDerived() { DerivedInvoked = true; } public bool DerivedInvoked { get; private set; } } public class ReferencingPostDeserializationMock { public ReferencingPostDeserializationMock() { mock = new ReferencedPostDeserializationMock(); } [LatePostDeserializationAttribute] private void PostDeserialization() { if(mock.TestObject == null) { throw new InvalidOperationException("Referenced mock was still not deserialized when invoking hook."); } } private readonly ReferencedPostDeserializationMock mock; } public class ReferencedPostDeserializationMock { public ReferencedPostDeserializationMock() { TestObject = new object(); } public object TestObject { get; private set; } } public class CyclicReferenceMockA { public CyclicReferenceMockB B { get; set; } public string Str { get; set; } public CyclicReferenceMockA() { Str = "Something"; } [LatePostDeserializationAttribute] public void TestIfBIsReady() { if(B == null || B.Str == null) { throw new InvalidOperationException("B is not ready after deserialization."); } } } public class CyclicReferenceMockB { public CyclicReferenceMockA A { get; set; } public string Str { get; set; } public CyclicReferenceMockB() { Str = "Something different"; } [LatePostDeserializationAttribute] public void TestIfAIsReady() { if(A == null || A.Str == null) { throw new InvalidOperationException("A is not ready after deserialization."); } } } public class LatePostSerializationMockA { public LatePostSerializationMockA() { B = new PostSerializationMockB(); } public PostSerializationMockB B { get; set; } [LatePostSerialization] private void PostSerialization() { if(!B.PostSerialized) { throw new InvalidOperationException("Late post serialization hook happened earlier than immediate one on referenced class."); } } } public class PostSerializationMockB { public bool PostSerialized { get; private set; } [PostSerializationAttribute] private void PostSerialization() { PostSerialized = true; } } public class LateDeserializationMockA { public LateDeserializationMockA() { B = new DeserializationMockB(); } public DeserializationMockB B { get; set; } [LatePostDeserializationAttribute] private void PostDeserialization() { if(!B.PostDeserialized) { throw new InvalidOperationException("Late post serialization hook happened earlier than immediate one on referenced class."); } } } public class DeserializationMockB { public bool PostDeserialized { get; private set; } [PostDeserializationAttribute] private void PostDeserialization() { PostDeserialized = true; } } public class ForOrderTestA : ForOrderTestB { public ForOrderTestB B { get; set; } } public class ForOrderTestB { public DateTime HookInvokedOn { get; private set; } [PostDeserialization] [PostSerialization] public void AfterDeOrSerialization() { HookInvokedOn = DateTime.Now; Thread.Sleep(100); } } public class ForLateOrderTestA : ForLateOrderTestB { public ForLateOrderTestB B { get; set; } } public class ForLateOrderTestB { public DateTime HookInvokedOn { get; private set; } [LatePostDeserialization] [LatePostSerialization] public void AfterDeOrSerialization() { HookInvokedOn = DateTime.Now; Thread.Sleep(100); } } public class ClassSendingExcetpionDuringSerialization { [PreSerialization] private void SendException() { throw new InvalidOperationException(); } } public interface IPrePostMock { bool PreExecuted { get; } bool PostExecuted { get; } } public class PrePostSerializationMock : IPrePostMock { public PrePostSerializationMock() { sendingException = new ClassSendingExcetpionDuringSerialization(); } public bool PreExecuted { get; private set; } public bool PostExecuted { get; private set; } [PreSerialization] private void BeforeSerialization() { PreExecuted = true; } [PostSerialization] private void AfterSerialization() { PostExecuted = true; } #pragma warning disable 0414 private ClassSendingExcetpionDuringSerialization sendingException; #pragma warning restore 0414 } public class LatePrePostSerializationMock : IPrePostMock { public LatePrePostSerializationMock() { sendingException = new ClassSendingExcetpionDuringSerialization(); } public bool PreExecuted { get; private set; } public bool PostExecuted { get; private set; } [PreSerialization] private void BeforeSerialization() { PreExecuted = true; } [LatePostSerialization] private void AfterSerialization() { PostExecuted = true; } #pragma warning disable 0414 private ClassSendingExcetpionDuringSerialization sendingException; #pragma warning restore 0414 } public class BaseClassA { [Transient] protected bool FlagA; [PostDeserialization] private void AfterDeserialization() { FlagA = true; } } public class DerivedFromBaseClassA : BaseClassA { [Transient] protected bool FlagB; [PostDeserialization] private void AfterDeserialization() { Assert.IsTrue(FlagA); FlagB = true; } } public class DerivedFromClassB : DerivedFromBaseClassA { [Transient] public bool FlagC; [PostDeserialization] private void AfterDeserialization() { Assert.IsTrue(FlagA); Assert.IsTrue(FlagB); FlagC = true; } } public class MockWithVirtualBase { public int BasePreSerializationCounter { get; private set; } public int BasePostSerializationCounter { get; private set; } public int BasePostDeserializationCounter { get; private set; } [PreSerialization] protected virtual void BeforeSerialization() { BasePreSerializationCounter = Counter; } [PostSerialization] protected virtual void AfterSerialization() { BasePostSerializationCounter = Counter; } [PostDeserialization] protected virtual void AfterDeserialization() { BasePostDeserializationCounter = Counter; } protected int Counter { get { return ++counterValue; } } private int counterValue; } public class MockWithVirtualDerived : MockWithVirtualBase { public int DerivedPreSerializationCounter { get; private set; } public int DerivedPostSerializationCounter { get; private set; } public int DerivedPostDeserializationCounter { get; private set; } protected override void BeforeSerialization() { base.BeforeSerialization(); DerivedPreSerializationCounter = Counter; } protected override void AfterSerialization() { base.AfterSerialization(); DerivedPostSerializationCounter = Counter; } protected override void AfterDeserialization() { base.AfterDeserialization(); DerivedPostDeserializationCounter = Counter; } } }