Skip to content

Commit

Permalink
Fix PooledArrayBufferWriter.AsMemory (#8300)
Browse files Browse the repository at this point in the history
* Fix PooledArrayBufferWriter.AsMemory length

* Improve support for IDerivedTypeCopier with generic types

* Increase test coverage, especially for JSON

* Fix test for PooledArrayBufferWriter
  • Loading branch information
ReubenBond authored Feb 1, 2023
1 parent 2dc044e commit 58b072e
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 21 deletions.
182 changes: 182 additions & 0 deletions src/Orleans.Serialization.TestKit/FieldCodecTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Xunit;
using Orleans.Serialization.Serializers;
using Xunit.Abstractions;
using System.Threading;

namespace Orleans.Serialization.TestKit
{
Expand Down Expand Up @@ -438,6 +439,13 @@ void Test(TValue original)
Assert.Equal(expected, result);
}

{
var writer = Writer.CreatePooled(_sessionPool.GetSession());
serializer.Serialize(original, ref writer);
var result = writer.Output.ToArray();
Assert.Equal(expected, result);
}

var bytes = new byte[10240];

{
Expand Down Expand Up @@ -497,6 +505,180 @@ void Test(TValue original)
}
}

[Fact]
public void CanRoundTripCollectionViaSerializer()
{
var serializer = _serviceProvider.GetRequiredService<Serializer<List<TValue>>>();

var original = new List<TValue>();
original.AddRange(TestValues);
for (var i = 0; i < 5; i++)
{
original.Add(CreateValue());
}

using var writerSession = _sessionPool.GetSession();
var writer = Writer.CreatePooled(writerSession);
serializer.Serialize(original, ref writer);
using var readerSession = _sessionPool.GetSession();
var reader = Reader.Create(writer.Output.AsReadOnlySequence(), readerSession);
var deserialized = serializer.Deserialize(ref reader);

Assert.Equal(original.Count, deserialized.Count);
for (var i = 0; i < original.Count; ++i)
{
var isEqual = Equals(original[i], deserialized[i]);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value at index {i}, \"{deserialized}\", must equal original value, \"{original}\"");
}

Assert.Equal(writer.Position, reader.Position);
Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId);
}

[Fact]
public void CanCopyCollectionViaSerializer()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<List<TValue>>>();

var original = new List<TValue>();
original.AddRange(TestValues);
for (var i = 0; i < 5; i++)
{
original.Add(CreateValue());
}

var copy = copier.Copy(original);

Assert.Equal(original.Count, copy.Count);
for (var i = 0; i < original.Count; ++i)
{
var isEqual = Equals(original[i], copy[i]);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value at index {i}, \"{copy}\", must equal original value, \"{original}\"");
}
}

[Fact]
public void CanCopyCollectionViaUntypedSerializer()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<List<object>>>();

var original = new List<object>();
foreach (var value in TestValues)
{
original.Add(value);
}

for (var i = 0; i < 5; i++)
{
original.Add(CreateValue());
}

var copy = copier.Copy(original);

Assert.Equal(original.Count, copy.Count);
for (var i = 0; i < original.Count; ++i)
{
var isEqual = Equals((TValue)original[i], (TValue)copy[i]);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value at index {i}, \"{copy}\", must equal original value, \"{original}\"");
}
}

[Fact]
public void CanCopyTupleViaSerializer_Untyped()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<(string, object, object, string)>>();
var value = TestValues.Reverse().Concat(new[] { CreateValue(), CreateValue() }).Take(2).ToArray();

var original = (Guid.NewGuid().ToString(), (object)value[0], (object)value[1], Guid.NewGuid().ToString());

var copy = copier.Copy(original);

var isEqual = Equals(original.Item1, copy.Item1);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 1, \"{copy.Item1}\", must equal original value, \"{original.Item1}\"");
isEqual = Equals((TValue)original.Item2, (TValue)copy.Item2);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 2, \"{copy.Item2}\", must equal original value, \"{original.Item2}\"");
isEqual = Equals((TValue)original.Item3, (TValue)copy.Item3);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 3, \"{copy.Item3}\", must equal original value, \"{original.Item3}\"");
isEqual = Equals(original.Item4, copy.Item4);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 4, \"{copy.Item4}\", must equal original value, \"{original.Item4}\"");
}

[Fact]
public void CanCopyTupleViaSerializer()
{
var copier = _serviceProvider.GetRequiredService<DeepCopier<(string, TValue, TValue, string)>>();

var original = (Guid.NewGuid().ToString(), CreateValue(), CreateValue(), Guid.NewGuid().ToString());

var copy = copier.Copy(original);

var isEqual = Equals(original.Item1, copy.Item1);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 1, \"{copy}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item2, copy.Item2);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 2, \"{copy}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item3, copy.Item3);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 3, \"{copy}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item4, copy.Item4);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Copied value for item 4, \"{copy}\", must equal original value, \"{original}\"");
}

[Fact]
public void CanRoundTripTupleViaSerializer()
{
var serializer = _serviceProvider.GetRequiredService<Serializer<(string, TValue, TValue, string)>>();

var original = (Guid.NewGuid().ToString(), CreateValue(), CreateValue(), Guid.NewGuid().ToString());

using var writerSession = _sessionPool.GetSession();
var writer = Writer.CreatePooled(writerSession);
serializer.Serialize(original, ref writer);
using var readerSession = _sessionPool.GetSession();
var reader = Reader.Create(writer.Output.AsReadOnlySequence(), readerSession);
var deserialized = serializer.Deserialize(ref reader);

var isEqual = Equals(original.Item1, deserialized.Item1);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 1, \"{deserialized}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item2, deserialized.Item2);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 2, \"{deserialized}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item3, deserialized.Item3);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 3, \"{deserialized}\", must equal original value, \"{original}\"");
isEqual = Equals(original.Item4, deserialized.Item4);
Assert.True(
isEqual,
isEqual ? string.Empty : $"Deserialized value for item 4, \"{deserialized}\", must equal original value, \"{original}\"");

Assert.Equal(writer.Position, reader.Position);
Assert.Equal(writerSession.ReferencedObjects.CurrentReferenceId, readerSession.ReferencedObjects.CurrentReferenceId);
}

[Fact]
public void CanRoundTripViaSerializer()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public Memory<byte> AsMemory(int offset)
#if NET6_0_OR_GREATER
if (IsStandardSize)
{
return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length);
return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length - offset);
}
#endif

Expand Down
13 changes: 11 additions & 2 deletions src/Orleans.Serialization/Serializers/CodecProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -637,10 +637,19 @@ private IDeepCopier CreateCopierInstance(Type fieldType, Type searchType)
{
copierType = surrogateCodecType;
}
else if (searchType.BaseType is { } baseType && CreateCopierInstance(fieldType, baseType) is IDerivedTypeCopier baseCopier)
else if (searchType.BaseType is { } baseType)
{
// Find copiers which generalize over all subtypes.
return baseCopier;
if (CreateCopierInstance(fieldType, baseType) is IDerivedTypeCopier baseCopier)
{
return baseCopier;
}
else if (baseType.IsGenericType
&& baseType.IsConstructedGenericType
&& CreateCopierInstance(fieldType, baseType.GetGenericTypeDefinition()) is IDerivedTypeCopier genericBaseCopier)
{
return genericBaseCopier;
}
}

return copierType != null ? (IDeepCopier)GetServiceOrCreateInstance(copierType, constructorArguments) : null;
Expand Down
41 changes: 24 additions & 17 deletions test/Orleans.Serialization.UnitTests/JsonSerializerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Serialization.Cloning;
using Orleans.Serialization.Codecs;
Expand All @@ -10,7 +17,7 @@
namespace Orleans.Serialization.UnitTests
{
[Trait("Category", "BVT")]
public class JsonCodecTests : FieldCodecTester<MyJsonClass, IFieldCodec<MyJsonClass>>
public class JsonCodecTests : FieldCodecTester<MyJsonClass?, IFieldCodec<MyJsonClass?>>
{
public JsonCodecTests(ITestOutputHelper output) : base(output)
{
Expand All @@ -21,20 +28,20 @@ protected override void Configure(ISerializerBuilder builder)
builder.AddJsonSerializer(isSupported: type => type.GetCustomAttribute<MyJsonSerializableAttribute>(inherit: false) is not null);
}

protected override MyJsonClass CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello" };
protected override MyJsonClass? CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello", Id = new(Guid.NewGuid()) };

protected override MyJsonClass[] TestValues => new MyJsonClass[]
protected override MyJsonClass?[] TestValues => new MyJsonClass?[]
{
null,
new MyJsonClass(),
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000) },
new MyJsonClass() { Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20), Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000), Id = new(Guid.NewGuid()) },
};

[Fact]
public void JsonSerializerDeepCopyTyped()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var copier = ServiceProvider.GetRequiredService<DeepCopier<MyJsonClass>>();
var result = copier.Copy(original);

Expand All @@ -45,7 +52,7 @@ public void JsonSerializerDeepCopyTyped()
[Fact]
public void JsonSerializerDeepCopyUntyped()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var copier = ServiceProvider.GetRequiredService<DeepCopier>();
var result = (MyJsonClass)copier.Copy((object)original);

Expand All @@ -56,7 +63,7 @@ public void JsonSerializerDeepCopyUntyped()
[Fact]
public void JsonSerializerRoundTripThroughCodec()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var result = RoundTripThroughCodec(original);

Assert.Equal(original.IntProperty, result.IntProperty);
Expand All @@ -66,7 +73,7 @@ public void JsonSerializerRoundTripThroughCodec()
[Fact]
public void JsonSerializerRoundTripThroughUntypedSerializer()
{
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi" };
var original = new MyJsonClass { IntProperty = 30, SubTypeProperty = "hi", Id = new(Guid.NewGuid()) };
var untypedResult = RoundTripThroughUntypedSerializer(original, out _);

var result = Assert.IsType<MyJsonClass>(untypedResult);
Expand All @@ -76,7 +83,7 @@ public void JsonSerializerRoundTripThroughUntypedSerializer()
}

[Trait("Category", "BVT")]
public class JsonCodecCopierTests : CopierTester<MyJsonClass, IDeepCopier<MyJsonClass>>
public class JsonCodecCopierTests : CopierTester<MyJsonClass?, IDeepCopier<MyJsonClass?>>
{
public JsonCodecCopierTests(ITestOutputHelper output) : base(output)
{
Expand All @@ -86,17 +93,17 @@ protected override void Configure(ISerializerBuilder builder)
{
builder.AddJsonSerializer(isSupported: type => type.GetCustomAttribute<MyJsonSerializableAttribute>(inherit: false) is not null);
}
protected override IDeepCopier<MyJsonClass> CreateCopier() => ServiceProvider.GetRequiredService<ICodecProvider>().GetDeepCopier<MyJsonClass>();
protected override IDeepCopier<MyJsonClass?> CreateCopier() => ServiceProvider.GetRequiredService<ICodecProvider>().GetDeepCopier<MyJsonClass?>();


protected override MyJsonClass CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello" };
protected override MyJsonClass? CreateValue() => new MyJsonClass { IntProperty = 30, SubTypeProperty = "hello", Id = new(Guid.NewGuid()) };

protected override MyJsonClass[] TestValues => new MyJsonClass[]
protected override MyJsonClass?[] TestValues => new MyJsonClass?[]
{
null,
new MyJsonClass(),
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000) },
new MyJsonClass() { Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = 150, SubTypeProperty = new string('c', 20), Id = new(Guid.NewGuid()) },
new MyJsonClass() { IntProperty = -150_000, SubTypeProperty = new string('c', 6_000), Id = new(Guid.NewGuid()) },
};
}
}
Loading

0 comments on commit 58b072e

Please sign in to comment.