diff --git a/src/FasterKv.Cache.Core/Abstractions/ISystemClock.cs b/src/FasterKv.Cache.Core/Abstractions/ISystemClock.cs
index 87a472e..379218b 100644
--- a/src/FasterKv.Cache.Core/Abstractions/ISystemClock.cs
+++ b/src/FasterKv.Cache.Core/Abstractions/ISystemClock.cs
@@ -5,6 +5,8 @@ namespace FasterKv.Cache.Core.Abstractions;
public interface ISystemClock
{
DateTimeOffset Now();
+
+ long NowUnixTimestamp();
}
public sealed class DefaultSystemClock : ISystemClock
@@ -13,4 +15,9 @@ public DateTimeOffset Now()
{
return DateTimeOffset.Now;
}
+
+ public long NowUnixTimestamp()
+ {
+ return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ }
}
\ No newline at end of file
diff --git a/src/FasterKv.Cache.Core/Abstractions/ValueWrapper.cs b/src/FasterKv.Cache.Core/Abstractions/ValueWrapper.cs
index 9b7e6bd..04b0573 100644
--- a/src/FasterKv.Cache.Core/Abstractions/ValueWrapper.cs
+++ b/src/FasterKv.Cache.Core/Abstractions/ValueWrapper.cs
@@ -1,5 +1,6 @@
using System;
using System.Buffers;
+using FasterKv.Cache.Core.Serializers;
namespace FasterKv.Cache.Core;
@@ -29,9 +30,31 @@ public ValueWrapper(T? data, long? expiryTime = null)
///
/// HasExpired
///
- /// Now
+ /// Now
/// value has expired
- public bool HasExpired(DateTimeOffset now) => now.ToUnixTimeMilliseconds() > ExpiryTime;
+ public bool HasExpired(long nowTimestamp) => nowTimestamp > ExpiryTime;
+
+ ///
+ /// Get FasterKvSerializerFlags
+ ///
+ ///
+ ///
+ internal FasterKvSerializerFlags GetFlags(long nowTimestamp)
+ {
+ var flags = FasterKvSerializerFlags.None;
+ if (ExpiryTime is not null)
+ {
+ flags |= FasterKvSerializerFlags.HasExpiryTime;
+ }
+
+ // don't serializer expired value body
+ if (Data is not null && HasExpired(nowTimestamp) == false)
+ {
+ flags |= FasterKvSerializerFlags.HasBody;
+ }
+
+ return flags;
+ }
}
internal sealed class ValueWrapper
@@ -63,9 +86,31 @@ public ValueWrapper(object? data, long? expiryTime = null)
///
/// HasExpired
///
- /// Now
+ /// Now
/// value has expired
- public bool HasExpired(DateTimeOffset now) => now.ToUnixTimeMilliseconds() > ExpiryTime;
+ public bool HasExpired(long nowTimestamp) => nowTimestamp > ExpiryTime;
+
+ ///
+ /// Get FasterKvSerializerFlags
+ ///
+ ///
+ ///
+ internal FasterKvSerializerFlags GetFlags(long nowTimestamp)
+ {
+ var flags = FasterKvSerializerFlags.None;
+ if (ExpiryTime is not null)
+ {
+ flags |= FasterKvSerializerFlags.HasExpiryTime;
+ }
+
+ // don't serializer expired value body
+ if (Data is not null && HasExpired(nowTimestamp) == false)
+ {
+ flags |= FasterKvSerializerFlags.HasBody;
+ }
+
+ return flags;
+ }
///
/// Get TValue From Data or DataBytes
diff --git a/src/FasterKv.Cache.Core/FasterKvCache.cs b/src/FasterKv.Cache.Core/FasterKvCache.cs
index eed4cae..fcb6a53 100644
--- a/src/FasterKv.Cache.Core/FasterKvCache.cs
+++ b/src/FasterKv.Cache.Core/FasterKvCache.cs
@@ -52,7 +52,7 @@ public FasterKvCache(string name,
var serializer = new SerializerSettings
{
keySerializer = () => new StringSerializer(),
- valueSerializer = () => new FasterKvSerializer(_valueSerializer)
+ valueSerializer = () => new FasterKvSerializer(_valueSerializer, _systemClock)
};
_logSettings = options.GetLogSettings(name);
@@ -95,7 +95,7 @@ public FasterKvCache(string name,
return default;
}
- if (result.output.HasExpired(_systemClock.Now()))
+ if (result.output.HasExpired(_systemClock.NowUnixTimestamp()))
{
Delete(key);
return default;
@@ -139,7 +139,7 @@ public void Set(string key, TValue value, TimeSpan expiryTime)
return default;
}
- if (result.output.HasExpired(_systemClock.Now()))
+ if (result.output.HasExpired(_systemClock.NowUnixTimestamp()))
{
await DeleteAsync(key, token);
return default;
@@ -234,7 +234,7 @@ private async Task ExpiryScanLoop()
context.FinalizeRead(out result.status, out result.output);
}
- if (result.status.Found && result.output.HasExpired(_systemClock.Now()))
+ if (result.status.Found && result.output.HasExpired(_systemClock.NowUnixTimestamp()))
{
sessionWrap.Session.Delete(key);
}
diff --git a/src/FasterKv.Cache.Core/FasterKvStore.TValue.cs b/src/FasterKv.Cache.Core/FasterKvStore.TValue.cs
index 451724e..35ffd73 100644
--- a/src/FasterKv.Cache.Core/FasterKvStore.TValue.cs
+++ b/src/FasterKv.Cache.Core/FasterKvStore.TValue.cs
@@ -50,7 +50,7 @@ public FasterKvCache(string name,
var serializer = new SerializerSettings>
{
keySerializer = () => new StringSerializer(),
- valueSerializer = () => new FasterKvSerializer(valueSerializer)
+ valueSerializer = () => new FasterKvSerializer(valueSerializer, _systemClock)
};
_logSettings = options.GetLogSettings(name);
@@ -88,7 +88,7 @@ public FasterKvCache(string name,
context.FinalizeRead(out result.status, out result.output);
}
- if (result.output.HasExpired(_systemClock.Now()))
+ if (result.output.HasExpired(_systemClock.NowUnixTimestamp()))
{
Delete(key);
return default;
@@ -133,7 +133,7 @@ public void Set(string key, TValue value, TimeSpan expiryTime)
using var scopeSession = GetSessionWrap();
var result = (await scopeSession.Session.ReadAsync(ref key, token: token)).Complete();
- if (result.output.HasExpired(_systemClock.Now()))
+ if (result.output.HasExpired(_systemClock.NowUnixTimestamp()))
{
await DeleteAsync(key, token);
return default;
@@ -220,7 +220,7 @@ private async Task ExpiryScanLoop()
context.FinalizeRead(out result.status, out result.output);
}
- if (result.status.Found && result.output.HasExpired(_systemClock.Now()))
+ if (result.status.Found && result.output.HasExpired(_systemClock.NowUnixTimestamp()))
{
sessionWrap.Session.Delete(key);
}
diff --git a/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.TValue.cs b/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.TValue.cs
index 991a47e..3f388ce 100644
--- a/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.TValue.cs
+++ b/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.TValue.cs
@@ -1,61 +1,74 @@
using System.Buffers;
using FASTER.core;
+using FasterKv.Cache.Core.Abstractions;
namespace FasterKv.Cache.Core.Serializers;
internal sealed class FasterKvSerializer : BinaryObjectSerializer>
{
+ private readonly ISystemClock _systemClock;
private readonly IFasterKvCacheSerializer _serializer;
- public FasterKvSerializer(IFasterKvCacheSerializer serializer)
+ public FasterKvSerializer(IFasterKvCacheSerializer serializer, ISystemClock systemClock)
{
_serializer = serializer.ArgumentNotNull();
+ _systemClock = systemClock.ArgumentNotNull();
}
public override void Deserialize(out ValueWrapper obj)
{
obj = new ValueWrapper();
- var etNullFlag = reader.ReadByte();
- if (etNullFlag == 1)
+ var flags = (FasterKvSerializerFlags)reader.ReadByte();
+ if ((flags & FasterKvSerializerFlags.HasExpiryTime) == FasterKvSerializerFlags.HasExpiryTime)
{
obj.ExpiryTime = reader.ReadInt64();
}
- var dataLength = reader.ReadInt32();
- var buffer = ArrayPool.Shared.Rent(dataLength);
- try
+ if ((flags & FasterKvSerializerFlags.HasBody) == FasterKvSerializerFlags.HasBody)
{
- _ = reader.Read(buffer, 0, dataLength);
- obj.Data = _serializer.Deserialize(buffer, dataLength);
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
+ var dataLength = reader.ReadInt32();
+ if (obj.HasExpired(_systemClock.NowUnixTimestamp()))
+ {
+ reader.BaseStream.Position += dataLength;
+ }
+ else
+ {
+ var buffer = ArrayPool.Shared.Rent(dataLength);
+ try
+ {
+ _ = reader.Read(buffer, 0, dataLength);
+ obj.Data = _serializer.Deserialize(buffer, dataLength);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
}
+
}
public override void Serialize(ref ValueWrapper obj)
{
- if (obj.ExpiryTime is null)
- {
- // write Expiry Time is null flag
- writer.Write((byte)0);
- }
- else
+ var flags = obj.GetFlags(_systemClock.NowUnixTimestamp());
+ writer.Write((byte)flags);
+ if ((flags & FasterKvSerializerFlags.HasExpiryTime) == FasterKvSerializerFlags.HasExpiryTime)
{
- writer.Write((byte)1);
- writer.Write(obj.ExpiryTime.Value);
+ writer.Write(obj.ExpiryTime!.Value);
}
- var beforePos = writer.BaseStream.Position;
- var dataPos = writer.BaseStream.Position = writer.BaseStream.Position += sizeof(int);
- _serializer.Serialize(writer.BaseStream, obj.Data);
- var afterPos = writer.BaseStream.Position;
+ if ((flags & FasterKvSerializerFlags.HasBody) == FasterKvSerializerFlags.HasBody)
+ {
+ var beforePos = writer.BaseStream.Position;
+ var dataPos = writer.BaseStream.Position = writer.BaseStream.Position += sizeof(int);
+ _serializer.Serialize(writer.BaseStream, obj.Data);
+ var afterPos = writer.BaseStream.Position;
- var length = (int)(afterPos - dataPos);
- writer.BaseStream.Position = beforePos;
+ var length = (int)(afterPos - dataPos);
+ writer.BaseStream.Position = beforePos;
- writer.Write(length);
- writer.BaseStream.Position = afterPos;
+ writer.Write(length);
+ writer.BaseStream.Position = afterPos;
+ }
}
}
\ No newline at end of file
diff --git a/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.cs b/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.cs
index c7a4ca2..cf4ad56 100644
--- a/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.cs
+++ b/src/FasterKv.Cache.Core/Serializers/FasterKvSerializer.cs
@@ -1,53 +1,75 @@
-using System.Buffers;
+using System;
+using System.Buffers;
using FASTER.core;
+using FasterKv.Cache.Core.Abstractions;
namespace FasterKv.Cache.Core.Serializers;
internal sealed class FasterKvSerializer : BinaryObjectSerializer
{
+ private readonly ISystemClock _systemClock;
private readonly IFasterKvCacheSerializer _serializer;
- public FasterKvSerializer(IFasterKvCacheSerializer serializer)
+ public FasterKvSerializer(IFasterKvCacheSerializer serializer, ISystemClock systemClock)
{
+ _systemClock = systemClock.ArgumentNotNull();
_serializer = serializer.ArgumentNotNull();
}
public override void Deserialize(out ValueWrapper obj)
{
obj = new ValueWrapper();
- var etNullFlag = reader.ReadByte();
- if (etNullFlag == 1)
+ var flags = (FasterKvSerializerFlags)reader.ReadByte();
+ if ((flags & FasterKvSerializerFlags.HasExpiryTime) == FasterKvSerializerFlags.HasExpiryTime)
{
obj.ExpiryTime = reader.ReadInt64();
}
- obj.DataByteLength = reader.ReadInt32();
- obj.DataBytes = ArrayPool.Shared.Rent(obj.DataByteLength);
- _ = reader.Read(obj.DataBytes, 0, obj.DataByteLength);
+ if ((flags & FasterKvSerializerFlags.HasBody) == FasterKvSerializerFlags.HasBody)
+ {
+ obj.DataByteLength = reader.ReadInt32();
+ if (obj.HasExpired(_systemClock.NowUnixTimestamp()))
+ {
+ reader.BaseStream.Position += obj.DataByteLength;
+ obj.DataByteLength = 0;
+ }
+ else
+ {
+ obj.DataBytes = ArrayPool.Shared.Rent(obj.DataByteLength);
+ _ = reader.Read(obj.DataBytes, 0, obj.DataByteLength);
+ }
+ }
}
public override void Serialize(ref ValueWrapper obj)
{
- if (obj.ExpiryTime is null)
- {
- // write Expiry Time is null flag
- writer.Write((byte)0);
- }
- else
+ var flags = obj.GetFlags(_systemClock.NowUnixTimestamp());
+ writer.Write((byte)flags);
+ if ((flags & FasterKvSerializerFlags.HasExpiryTime) == FasterKvSerializerFlags.HasExpiryTime)
{
- writer.Write((byte)1);
- writer.Write(obj.ExpiryTime.Value);
+ writer.Write(obj.ExpiryTime!.Value);
}
- var beforePos = writer.BaseStream.Position;
- var dataPos = writer.BaseStream.Position = writer.BaseStream.Position += sizeof(int);
- _serializer.Serialize(writer.BaseStream, obj.Data);
- var afterPos = writer.BaseStream.Position;
+ if ((flags & FasterKvSerializerFlags.HasBody) == FasterKvSerializerFlags.HasBody)
+ {
+ var beforePos = writer.BaseStream.Position;
+ var dataPos = writer.BaseStream.Position = writer.BaseStream.Position += sizeof(int);
+ _serializer.Serialize(writer.BaseStream, obj.Data);
+ var afterPos = writer.BaseStream.Position;
- var length = (int)(afterPos - dataPos);
- writer.BaseStream.Position = beforePos;
+ var length = (int)(afterPos - dataPos);
+ writer.BaseStream.Position = beforePos;
- writer.Write(length);
- writer.BaseStream.Position = afterPos;
+ writer.Write(length);
+ writer.BaseStream.Position = afterPos;
+ }
}
+}
+
+[Flags]
+internal enum FasterKvSerializerFlags : byte
+{
+ None = 0,
+ HasExpiryTime = 1 << 0,
+ HasBody = 1 << 1
}
\ No newline at end of file
diff --git a/tests/FasterKv.Cache.Core.Tests/FasterKv.Cache.Core.Tests.csproj b/tests/FasterKv.Cache.Core.Tests/FasterKv.Cache.Core.Tests.csproj
index 7b2424b..9b1a116 100644
--- a/tests/FasterKv.Cache.Core.Tests/FasterKv.Cache.Core.Tests.csproj
+++ b/tests/FasterKv.Cache.Core.Tests/FasterKv.Cache.Core.Tests.csproj
@@ -1,15 +1,17 @@
- net6.0
+ net7.0
enable
enable
-
false
+ true
+ 11
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreObjectTest.cs b/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreObjectTest.cs
index 2705494..a5e7e99 100644
--- a/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreObjectTest.cs
+++ b/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreObjectTest.cs
@@ -241,6 +241,60 @@ public void Set_Big_Value_Should_Success()
Assert.True(bigValues.SequenceEqual(result!));
}
}
+
+ [Fact]
+ public void Set_Big_DataSize_With_Expired_Should_Return_Null()
+ {
+ int nums = 1000;
+ for (int i = 0; i < nums; i++)
+ {
+ _fasterKv.Set($"Set_Big_DataSize_With_Expired_Should_Return_Null_{i}", new Data
+ {
+ One = i.ToString(),
+ Two = i
+ }, TimeSpan.FromSeconds(1));
+ }
+
+ Thread.Sleep(1000);
+
+ for (int i = 0; i < nums; i++)
+ {
+ var value = _fasterKv.Get($"Set_Big_DataSize_With_Expired_Should_Return_Null_{i}");
+ Assert.Null(value);
+ }
+ }
+
+ [Fact]
+ public void Set_Big_DataSize_With_Random_Expired_Should_Success()
+ {
+ int nums = 1000;
+ for (int i = 0; i < nums; i++)
+ {
+ _fasterKv.Set($"Set_Big_DataSize_With_Random_Expired_Should_Success_{i}", new Data
+ {
+ One = i.ToString(),
+ Two = i
+ }, i % 2 == 0 ? TimeSpan.FromSeconds(1) : TimeSpan.FromMinutes(1));
+ }
+
+ Thread.Sleep(1000);
+
+ for (int i = 0; i < nums; i++)
+ {
+ var value = _fasterKv.Get($"Set_Big_DataSize_With_Random_Expired_Should_Success_{i}");
+ if (i % 2 == 0)
+ {
+ Assert.Null(value);
+ }
+ else
+ {
+ Assert.NotNull(value);
+ Assert.NotNull(value);
+ Assert.Equal(i.ToString(), value!.One);
+ Assert.Equal(i, value.Two);
+ }
+ }
+ }
public void Dispose()
{
diff --git a/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreTest.cs b/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreTest.cs
index 8bf7e2b..7789acf 100644
--- a/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreTest.cs
+++ b/tests/FasterKv.Cache.Core.Tests/KvStore/FasterKvStoreTest.cs
@@ -273,6 +273,61 @@ public void Set_Big_Value_Should_Success()
Assert.True(bigValues.SequenceEqual(result.Three!));
}
}
+
+
+ [Fact]
+ public void Set_Big_DataSize_With_Expired_Should_Return_Null()
+ {
+ int nums = 1000;
+ for (int i = 0; i < nums; i++)
+ {
+ _fasterKv.Set($"Set_Big_DataSize_With_Expired_Should_Return_Null_{i}", new Data
+ {
+ One = i.ToString(),
+ Two = i
+ }, TimeSpan.FromSeconds(1));
+ }
+
+ Thread.Sleep(1000);
+
+ for (int i = 0; i < nums; i++)
+ {
+ var value = _fasterKv.Get($"Set_Big_DataSize_With_Expired_Should_Return_Null_{i}");
+ Assert.Null(value);
+ }
+ }
+
+ [Fact]
+ public void Set_Big_DataSize_With_Random_Expired_Should_Success()
+ {
+ int nums = 1000;
+ for (int i = 0; i < nums; i++)
+ {
+ _fasterKv.Set($"Set_Big_DataSize_With_Random_Expired_Should_Success_{i}", new Data
+ {
+ One = i.ToString(),
+ Two = i
+ }, i % 2 == 0 ? TimeSpan.FromSeconds(1) : TimeSpan.FromMinutes(1));
+ }
+
+ Thread.Sleep(1000);
+
+ for (int i = 0; i < nums; i++)
+ {
+ var value = _fasterKv.Get($"Set_Big_DataSize_With_Random_Expired_Should_Success_{i}");
+ if (i % 2 == 0)
+ {
+ Assert.Null(value);
+ }
+ else
+ {
+ Assert.NotNull(value);
+ Assert.NotNull(value);
+ Assert.Equal(i.ToString(), value!.One);
+ Assert.Equal(i, value.Two);
+ }
+ }
+ }
public void Dispose()
{
diff --git a/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.Deserialize.Tests.cs b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.Deserialize.Tests.cs
new file mode 100644
index 0000000..e288874
--- /dev/null
+++ b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.Deserialize.Tests.cs
@@ -0,0 +1,133 @@
+using System.Runtime.CompilerServices;
+using FasterKv.Cache.Core.Abstractions;
+using FasterKv.Cache.Core.Serializers;
+using Moq;
+
+namespace FasterKv.Cache.Core.Tests.Serializers;
+
+public class FasterKvSerializerDeserializeTests
+{
+ private unsafe Span ToSpan(ref T value)
+ {
+ return new Span(Unsafe.AsPointer(ref value), Unsafe.SizeOf());
+ }
+
+ [Fact]
+ public void Expired_Value_Should_Only_DeSerialize_ExpiryTime()
+ {
+ var mockKvCache = new Mock();
+
+ var mockClock = new Mock();
+ mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100);
+ var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object);
+
+ long timeStamp = 1020304;
+ // | flag | timestamp |
+ // | 1B | 8B |
+ using var ms = new MemoryStream();
+ ms.WriteByte((byte)FasterKvSerializerFlags.HasExpiryTime);
+ ms.Write(ToSpan(ref timeStamp));
+
+ ms.Position = 0;
+ ser.BeginDeserialize(ms);
+
+ ser.Deserialize(out var valueWrapper);
+
+ Assert.Equal(0, valueWrapper.DataByteLength);
+ Assert.Equal(timeStamp, valueWrapper.ExpiryTime);
+ Assert.Null(valueWrapper.DataBytes);
+ }
+
+ [Fact]
+ public void NotExpiry_Value_Should_Deserialize_All_Member()
+ {
+ var mockKvCache = new Mock();
+ var mockClock = new Mock();
+
+ mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100);
+ var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object);
+
+ using var ms = new MemoryStream();
+
+ // | flag | timestamp | data length | serialize length|
+ // | 1B | 8B | 4B | xxB |
+ ms.WriteByte((byte)(FasterKvSerializerFlags.HasExpiryTime | FasterKvSerializerFlags.HasBody));
+
+ long timeStamp = 1020304;
+ ms.Write(ToSpan(ref timeStamp));
+
+ ReadOnlySpan data = "hello world"u8;
+ int dataLength = data.Length;
+ ms.Write(ToSpan(ref dataLength));
+ ms.Write(data);
+
+ ms.Position = 0;
+ ser.BeginDeserialize(ms);
+
+ ser.Deserialize(out var wrapper);
+
+ Assert.Equal(timeStamp, wrapper.ExpiryTime);
+ Assert.Equal(dataLength, wrapper.DataByteLength);
+ for (int i = 0; i < wrapper.DataByteLength; i++)
+ {
+ Assert.Equal(data[i], wrapper.DataBytes![i]);
+ }
+ }
+
+ [Fact]
+ public void Not_Value_And_Not_ExpiryTime_Should_Only_Deserialize_Flag()
+ {
+ var mockKvCache = new Mock();
+
+ var mockClock = new Mock();
+ mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100);
+ var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object);
+
+ // | flag |
+ // | 1B |
+ using var ms = new MemoryStream();
+ ms.WriteByte((byte)FasterKvSerializerFlags.None);
+ ms.Position = 0;
+
+ ser.BeginDeserialize(ms);
+ ser.Deserialize(out var obj);
+
+ Assert.Null(obj.ExpiryTime);
+ Assert.Null(obj.DataBytes);
+ Assert.Null(obj.Data);
+ Assert.Equal(0, obj.DataByteLength);
+ }
+
+ [Fact]
+ public void Not_ExpiryTime_Should_Deserialize_Flag_DataLength_Value()
+ {
+ var mockKvCache = new Mock();
+ mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny