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())); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + // | flag | data length | serialize length | + // | 1B | 4B | xxB | + using var ms = new MemoryStream(); + ser.BeginDeserialize(ms); + + ReadOnlySpan data = "hello world"u8; + ms.WriteByte((byte)FasterKvSerializerFlags.HasBody); + var dataLength = data.Length; + ms.Write(ToSpan(ref dataLength)); + ms.Write(data); + + ms.Position = 0; + ser.BeginDeserialize(ms); + + ser.Deserialize(out var valueWrapper); + + Assert.Equal(dataLength, valueWrapper.DataByteLength); + for (int i = 0; i < valueWrapper.DataByteLength; i++) + { + Assert.Equal(data[i], valueWrapper.DataBytes![i]); + } + } +} \ No newline at end of file diff --git a/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.Serialize.Tests.cs b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.Serialize.Tests.cs new file mode 100644 index 0000000..c6265ac --- /dev/null +++ b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.Serialize.Tests.cs @@ -0,0 +1,109 @@ +using FasterKv.Cache.Core.Abstractions; +using FasterKv.Cache.Core.Serializers; +using Moq; + +namespace FasterKv.Cache.Core.Tests.Serializers; + +public class FasterKvSerializerSerializeTests +{ + [Fact] + public void Expired_Value_Should_Only_Serialize_ExpiryTime() + { + 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(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper("", 80); + ser.Serialize(ref wrapper); + + // | flag | timestamp | + // | 1B | 8B | + Assert.Equal(1 + 8, ms.Position); + } + + [Fact] + public void NotExpiry_Value_Should_Serialize_All_Member() + { + int serializeLength = 10; + + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny())).Callback( + (stream, data) => + { + stream.Position += serializeLength; + }); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + using var ms = new MemoryStream(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper("", 110); + ser.Serialize(ref wrapper); + + // | flag | timestamp | data length | serialize length| + // | 1B | 8B | 4B | 8B | + Assert.Equal(1 + 8 + 4 + serializeLength, ms.Position); + } + + [Fact] + public void Not_Value_And_Not_ExpiryTime_Should_Only_Serialize_Flag() + { + int serializeLength = 10; + + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny())).Callback( + (stream, data) => + { + stream.Position += serializeLength; + }); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + using var ms = new MemoryStream(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper(null, null); + ser.Serialize(ref wrapper); + + // | flag | + // | 1B | + Assert.Equal(1 , ms.Position); + } + + [Fact] + public void Not_ExpiryTime_Should_Serialize_Flag_DataLength_Value() + { + int serializeLength = 10; + + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny())).Callback( + (stream, data) => + { + stream.Position += serializeLength; + }); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + using var ms = new MemoryStream(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper("", null); + ser.Serialize(ref wrapper); + + // | flag | data length | serialize length | + // | 1B | 4B | 10B | + Assert.Equal(1 + 4 + 10, ms.Position); + } +} \ No newline at end of file diff --git a/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.TValue.Deserialize.Tests.cs b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.TValue.Deserialize.Tests.cs new file mode 100644 index 0000000..0dda7dc --- /dev/null +++ b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.TValue.Deserialize.Tests.cs @@ -0,0 +1,126 @@ +using System.Runtime.CompilerServices; +using System.Text; +using FasterKv.Cache.Core.Abstractions; +using FasterKv.Cache.Core.Serializers; +using Moq; + +namespace FasterKv.Cache.Core.Tests.Serializers; + +public class FasterKvSerializerTValueDeserializeTests +{ + 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(timeStamp, valueWrapper.ExpiryTime); + Assert.Null(valueWrapper.Data); + } + + [Fact] + public void NotExpiry_Value_Should_Deserialize_All_Member() + { + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Deserialize(It.IsAny(), It.IsAny())) + .Returns((bytes, length) => Encoding.UTF8.GetString(bytes, 0, length)); + 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("hello world", wrapper.Data); + } + + [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.Data); + } + + [Fact] + public void Not_ExpiryTime_Should_Deserialize_Flag_DataLength_Value() + { + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Deserialize(It.IsAny(), It.IsAny())) + .Returns((bytes, length) => Encoding.UTF8.GetString(bytes, 0, length)); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + // | flag | data length | serialize length | + // | 1B | 4B | xxB | + using var ms = new MemoryStream(); + ser.BeginDeserialize(ms); + + ReadOnlySpan data = "hello world"u8; + ms.WriteByte((byte)FasterKvSerializerFlags.HasBody); + var dataLength = data.Length; + ms.Write(ToSpan(ref dataLength)); + ms.Write(data); + + ms.Position = 0; + ser.BeginDeserialize(ms); + + ser.Deserialize(out var valueWrapper); + + Assert.Equal("hello world", valueWrapper.Data); + } +} \ No newline at end of file diff --git a/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.TValue.Serialize.Tests.cs b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.TValue.Serialize.Tests.cs new file mode 100644 index 0000000..1aa1686 --- /dev/null +++ b/tests/FasterKv.Cache.Core.Tests/Serializers/FasterKvSerializer.TValue.Serialize.Tests.cs @@ -0,0 +1,109 @@ +using FasterKv.Cache.Core.Abstractions; +using FasterKv.Cache.Core.Serializers; +using Moq; + +namespace FasterKv.Cache.Core.Tests.Serializers; + +public class FasterKvSerializerTValueSerializeTests +{ + [Fact] + public void Expired_Value_Should_Only_Serialize_ExpiryTime() + { + 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(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper("", 80); + ser.Serialize(ref wrapper); + + // | flag | timestamp | + // | 1B | 8B | + Assert.Equal(1 + 8, ms.Position); + } + + [Fact] + public void NotExpiry_Value_Should_Serialize_All_Member() + { + int serializeLength = 10; + + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny())).Callback( + (stream, data) => + { + stream.Position += serializeLength; + }); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + using var ms = new MemoryStream(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper("", 110); + ser.Serialize(ref wrapper); + + // | flag | timestamp | data length | serialize length| + // | 1B | 8B | 4B | 8B | + Assert.Equal(1 + 8 + 4 + serializeLength, ms.Position); + } + + [Fact] + public void Not_Value_And_Not_ExpiryTime_Should_Only_Serialize_Flag() + { + int serializeLength = 10; + + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny())).Callback( + (stream, data) => + { + stream.Position += serializeLength; + }); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + using var ms = new MemoryStream(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper(null, null); + ser.Serialize(ref wrapper); + + // | flag | + // | 1B | + Assert.Equal(1 , ms.Position); + } + + [Fact] + public void Not_ExpiryTime_Should_Serialize_Flag_DataLength_Value() + { + int serializeLength = 10; + + var mockKvCache = new Mock(); + mockKvCache.Setup(i => i.Serialize(It.IsAny(), It.IsAny())).Callback( + (stream, data) => + { + stream.Position += serializeLength; + }); + + var mockClock = new Mock(); + mockClock.Setup(i => i.NowUnixTimestamp()).Returns(100); + var ser = new FasterKvSerializer(mockKvCache.Object, mockClock.Object); + + using var ms = new MemoryStream(); + ser.BeginSerialize(ms); + + var wrapper = new ValueWrapper("", null); + ser.Serialize(ref wrapper); + + // | flag | data length | serialize length | + // | 1B | 4B | 10B | + Assert.Equal(1 + 4 + 10, ms.Position); + } +} \ No newline at end of file diff --git a/tests/FasterKv.Cache.Core.Tests/Serializers/MessagePackTests.cs b/tests/FasterKv.Cache.Core.Tests/Serializers/MessagePackTests.cs index 20d2607..e69de29 100644 --- a/tests/FasterKv.Cache.Core.Tests/Serializers/MessagePackTests.cs +++ b/tests/FasterKv.Cache.Core.Tests/Serializers/MessagePackTests.cs @@ -1,22 +0,0 @@ -using MessagePack; - -namespace FasterKv.Cache.Core.Tests.Serializers; - -public class MessagePackTests -{ - [Fact] - public void Serializer_Null_Should_Success() - { - Test obj = null!; - var bytes = MessagePackSerializer.Serialize(obj); - var result = MessagePackSerializer.Deserialize(bytes); - Assert.Equal(obj, result); - } -} - -[MessagePackObject] -public class Test -{ - [Key(0)] - public string Value { get; set; } = null!; -} \ No newline at end of file