From 773227069620ab148a29a81788387bd51ce1d204 Mon Sep 17 00:00:00 2001 From: James Courtney <2636896+jamescourtney@users.noreply.github.com> Date: Tue, 24 Aug 2021 02:24:23 -0700 Subject: [PATCH] Additional value struct tests (#199) * Additional value struct tests * More * More tests --- .../ExperimentalBenchmark/Program.cs | 26 +- .../TypeModel/ValueStructTypeModel.cs | 4 +- .../ValueStructs/ValueStructTests.cs | 6 +- .../OracleTests/AlignmentTests.cs | 2 +- .../SerializationTests/ValueStructTests.cs | 337 ++++++++++++++++++ 5 files changed, 367 insertions(+), 8 deletions(-) diff --git a/src/Benchmarks/ExperimentalBenchmark/Program.cs b/src/Benchmarks/ExperimentalBenchmark/Program.cs index ff2718ba..72fb68f5 100644 --- a/src/Benchmarks/ExperimentalBenchmark/Program.cs +++ b/src/Benchmarks/ExperimentalBenchmark/Program.cs @@ -33,7 +33,9 @@ public class Benchmark private readonly byte[] data = new byte[10 * 1024 * 1024]; private ArrayInputBuffer inputBuffer; - //private UnsafeSpanWriter2 spanWriter; + private ValueTable valueTable; + private ValueTable_Unsafe unsafeValueTable; + private Table table; [GlobalSetup] public void Setup() @@ -55,6 +57,10 @@ public void Setup() ValueTable.Serializer.Write(data, t); inputBuffer = new ArrayInputBuffer(data); + + valueTable = new ValueTable(ValueTable.Serializer.Parse(data)); + unsafeValueTable = new ValueTable_Unsafe(ValueTable_Unsafe.Serializer.Parse(data)); + table = new Table(Table.Serializer.Parse(data)); } [Benchmark] @@ -83,6 +89,12 @@ public int ParseAndTraverse_Value() return sum; } + [Benchmark] + public int Serialize_Value() + { + return ValueTable.Serializer.Write(this.data, this.valueTable); + } + [Benchmark] public int ParseAndTraverse_Value_Unsafe() { @@ -109,6 +121,12 @@ public int ParseAndTraverse_Value_Unsafe() return sum; } + [Benchmark] + public int Serialize_Value_Unsafe() + { + return ValueTable_Unsafe.Serializer.Write(this.data, this.unsafeValueTable); + } + [Benchmark] public int ParseAndTraverse_Ref() { @@ -134,6 +152,12 @@ public int ParseAndTraverse_Ref() return sum; } + + [Benchmark] + public int Serialize_Ref() + { + return Table.Serializer.Write(this.data, this.table); + } } public class Program diff --git a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs index 9a7f409f..2f6ba05d 100644 --- a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs +++ b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs @@ -261,9 +261,7 @@ public override void Initialize() throw new InvalidFlatBufferDefinitionException($"Can't create type model from type {this.ClrType.GetCompilableTypeName()} because it is not public."); } - this.CanMarshalWhenLittleEndian = - UnsafeSizeOf(this.ClrType) == this.inlineSize && - Marshal.SizeOf(this.ClrType) == this.inlineSize; + this.CanMarshalWhenLittleEndian = UnsafeSizeOf(this.ClrType) == this.inlineSize; } private static int UnsafeSizeOf(Type t) diff --git a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs index 1830d0ee..13f7829c 100644 --- a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs @@ -29,7 +29,7 @@ public class ValueStructTestCases [Fact] public void Basics() { - Assert.Equal(148, Marshal.SizeOf()); + Assert.Equal(148, Unsafe.SizeOf()); } [Fact] @@ -334,8 +334,8 @@ private static void AssertStructsEqual(ValueStruct a , ValueStruct b) Assert.Equal(a.D(i), b.D(i)); } - Span scratchA = stackalloc byte[Marshal.SizeOf()]; - Span scratchB = stackalloc byte[Marshal.SizeOf()]; + Span scratchA = stackalloc byte[Unsafe.SizeOf()]; + Span scratchB = stackalloc byte[Unsafe.SizeOf()]; MemoryMarshal.Cast(scratchA)[0] = a; MemoryMarshal.Cast(scratchB)[0] = b; diff --git a/src/Tests/FlatSharpTests/OracleTests/AlignmentTests.cs b/src/Tests/FlatSharpTests/OracleTests/AlignmentTests.cs index 441f0f7c..85b83d2b 100644 --- a/src/Tests/FlatSharpTests/OracleTests/AlignmentTests.cs +++ b/src/Tests/FlatSharpTests/OracleTests/AlignmentTests.cs @@ -193,7 +193,7 @@ public class StructVectors public FiveByteStruct AlignmentVec_0 { get; set; } [FlatBufferItem(1)] - public FiveByteStruct AlignmentVec_1 { get; set; } + public ValueFiveByteStruct AlignmentVec_1 { get; set; } [FlatBufferItem(2)] public FiveByteStruct AlignmentVec_2 { get; set; } diff --git a/src/Tests/FlatSharpTests/SerializationTests/ValueStructTests.cs b/src/Tests/FlatSharpTests/SerializationTests/ValueStructTests.cs index cd9c37d3..9de44cbe 100644 --- a/src/Tests/FlatSharpTests/SerializationTests/ValueStructTests.cs +++ b/src/Tests/FlatSharpTests/SerializationTests/ValueStructTests.cs @@ -75,6 +75,37 @@ public void InvalidStructs(Type structType, string expectedError) Assert.Equal(expectedError, ex.Message); } + [Theory] + [InlineData(typeof(ValidStruct_Marshallable), 20, sizeof(ulong))] + [InlineData(typeof(ValidStruct_Marshallable_OutOfOrder), 20, sizeof(ulong))] + [InlineData(typeof(FiveByteStruct), 5, sizeof(byte))] + [InlineData(typeof(NineByteStruct), 9, sizeof(ulong))] + [InlineData(typeof(SixteenByteStruct), 16, sizeof(ulong))] + public void LayoutTests(Type structType, int expectedSize, int expectedAlignment) + { + ValueStructTypeModel model = (ValueStructTypeModel)RuntimeTypeModel.CreateFrom(structType); + + var layout = Assert.Single(model.PhysicalLayout); + + Assert.False(model.SerializeMethodRequiresContext); + Assert.True(model.SerializesInline); + Assert.True(model.IsValidStructMember); + Assert.True(model.IsValidTableMember); + Assert.True(model.IsValidUnionMember); + Assert.True(model.IsValidVectorMember); + Assert.False(model.IsValidSortedVectorKey); + Assert.True(model.IsFixedSize); + + Assert.Equal(FlatBufferSchemaType.Struct, model.SchemaType); + Assert.Equal( + SerializationHelpers.GetMaxPadding(expectedAlignment) + expectedSize, + model.MaxInlineSize); + + Assert.Equal(expectedSize, layout.InlineSize); + Assert.Equal(expectedAlignment, layout.Alignment); + Assert.Equal(Marshal.SizeOf(structType), layout.InlineSize); + } + [FlatBufferStruct, StructLayout(LayoutKind.Sequential)] public struct InvalidStruct_Sequential { public byte A; } @@ -208,6 +239,96 @@ public void Serialize_NonNullable(Type type, bool enableMarshal, bool expectMars expectMarshal, serializer.CSharp.Contains($"MemoryMarshal.Cast")); } + + [Fact] + public void Serialize_NineByte() + { + byte[] data = + { + 4, 0, 0, 0, + 242, 255, 255, 255, + 1, 0, 0, 0, 0, 0, 0, 0, + 2, + 0, + 6, 0, + 13, 0, + 4, 0, + }; + + var table = new SimpleTableAnything + { + Item = new NineByteStruct + { + A = 1, + B = 2, + } + }; + + byte[] buffer = new byte[1024]; + int bytesWritten = FlatBufferSerializer.Default.Serialize(table, buffer); + + Assert.True(data.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, bytesWritten))); + } + + [Fact] + public void Serialize_FiveByte() + { + byte[] data = + { + 4, 0, 0, 0, + 246, 255, 255, 255, + 1, 2, 3, 4, 5, 0, + 6, 0, + 9, 0, + 4, 0, + }; + + SimpleTableAnything source = new() + { + Item = new FiveByteStruct + { + A = 1, + B = 2, + C = 3, + D = 4, + E = 5, + } + }; + + byte[] buffer = new byte[1024]; + var written = FlatBufferSerializer.Default.Serialize(source, buffer); + + Assert.True(data.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, written))); + } + + [Fact] + public void Serialize_SixteenByte() + { + byte[] data = + { + 4, 0, 0, 0, + 236, 255, 255, 255, + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + 6, 0, + 20, 0, + 4, 0, + }; + + SimpleTableAnything source = new() + { + Item = new SixteenByteStruct + { + A = 1, + B = 2ul, + } + }; + + byte[] buffer = new byte[1024]; + var written = FlatBufferSerializer.Default.Serialize(source, buffer); + + Assert.True(data.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, written))); + } } public class ParseTests @@ -295,6 +416,198 @@ public void Parse_NonNullable(Type type, bool enableMarshal, bool expectMarshal) Assert.Equal(4, table.Item.ID); Assert.Equal(5, table.Item.IInner.Test); } + + [Fact] + public void Parse_NineByte() + { + byte[] data = + { + 4, 0, 0, 0, + 242, 255, 255, 255, + 1, 0, 0, 0, 0, 0, 0, 0, + 2, + 0, + 6, 0, + 13, 0, + 4, 0, + }; + + var parsed = FlatBufferSerializer.Default.Parse>(data); + + Assert.NotNull(parsed.Item); + Assert.Equal(1ul, parsed.Item.Value.A); + Assert.Equal(2, parsed.Item.Value.B); + } + + [Fact] + public void Parse_FiveByte() + { + byte[] data = + { + 4, 0, 0, 0, + 246, 255, 255, 255, + 1, 2, 3, 4, 5, 0, + 6, 0, + 9, 0, + 4, 0, + }; + + var parsed = FlatBufferSerializer.Default.Parse>(data); + + Assert.NotNull(parsed.Item); + Assert.Equal(1, parsed.Item.Value.A); + Assert.Equal(2, parsed.Item.Value.B); + Assert.Equal(3, parsed.Item.Value.C); + Assert.Equal(4, parsed.Item.Value.D); + Assert.Equal(5, parsed.Item.Value.E); + } + + [Fact] + public void Parse_SixteenByte() + { + byte[] data = + { + 4, 0, 0, 0, + 236, 255, 255, 255, + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + 6, 0, + 20, 0, + 4, 0, + }; + + byte[] buffer = new byte[1024]; + var parsed = FlatBufferSerializer.Default.Parse< SimpleTableAnything>(data); + + Assert.NotNull(parsed); + Assert.NotNull(parsed.Item); + Assert.Equal(1, parsed.Item.Value.A); + Assert.Equal(2ul, parsed.Item.Value.B); + } + } + + public class FormattingTests + { + [Fact] + public void Bool() => RoundTrip(true, (ref ValueStruct_Bool s) => ref s.Value); + + [Fact] + public void Byte() => RoundTrip(byte.MaxValue, (ref ValueStruct_Byte s) => ref s.Value); + + [Fact] + public void SByte() => RoundTrip(sbyte.MinValue, (ref ValueStruct_SByte s) => ref s.Value); + + [Fact] + public void UShort() => RoundTrip(ushort.MaxValue, (ref ValueStruct_UShort s) => ref s.Value); + + [Fact] + public void Short() => RoundTrip(short.MinValue, (ref ValueStruct_Short s) => ref s.Value); + + [Fact] + public void UInt() => RoundTrip(uint.MaxValue, (ref ValueStruct_UInt s) => ref s.Value); + + [Fact] + public void Int() => RoundTrip(int.MinValue, (ref ValueStruct_Int s) => ref s.Value); + + [Fact] + public void ULong() => RoundTrip(ulong.MaxValue, (ref ValueStruct_ULong s) => ref s.Value); + + [Fact] + public void Long() => RoundTrip(long.MinValue, (ref ValueStruct_Long s) => ref s.Value); + + [Fact] + public void Double() => RoundTrip(Math.PI, (ref ValueStruct_Double s) => ref s.Value); + + [Fact] + public void Float() => RoundTrip((float)Math.E, (ref ValueStruct_Float s) => ref s.Value); + + private static void RoundTrip(TValue value, GetValue getter) + where TValue : struct + where TStruct : struct + { + SimpleTableAnything> refTable = new() + { + Item = new() { Item = value } + }; + + TStruct valueItem = default; + getter(ref valueItem) = value; + + SimpleTableAnything valueTable = new() + { + Item = valueItem + }; + + ValueStructTypeModel vstm = (ValueStructTypeModel)RuntimeTypeModel.CreateFrom(typeof(TStruct)); + Assert.True(vstm.CanMarshalWhenLittleEndian); + + byte[] referenceBuffer = new byte[1024]; + byte[] referenceBufferNull = new byte[1024]; + byte[] valueBufferNull = new byte[1024]; + byte[] valueBuffer = new byte[1024]; + + int refNullBytesWritten = FlatBufferSerializer.Default.Serialize(new SimpleTableAnything>(), referenceBufferNull); + int valueNullBytesWritten = FlatBufferSerializer.Default.Serialize(new SimpleTableAnything(), valueBufferNull); + + int refBytesWritten = FlatBufferSerializer.Default.Serialize(refTable, referenceBuffer); + int valueBytesWritten = FlatBufferSerializer.Default.Serialize(valueTable, valueBuffer); + + Assert.Equal(refBytesWritten, valueBytesWritten); + Assert.True(referenceBuffer.SequenceEqual(valueBuffer)); + + Assert.Equal(refNullBytesWritten, valueNullBytesWritten); + Assert.True(referenceBufferNull.SequenceEqual(valueBufferNull)); + + var parsed = FlatBufferSerializer.Default.Parse>(valueBuffer); + TStruct temp = parsed.Item; + Assert.Equal(value, getter(ref temp)); + + parsed = FlatBufferSerializer.Default.Parse>(referenceBufferNull); + temp = parsed.Item; + Assert.Equal(default(TValue), getter(ref temp)); + } + + public delegate ref TValue GetValue(ref TStruct @struct) where TStruct : struct; + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Bool {[FieldOffset(0)] public bool Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Byte { [FieldOffset(0)] public byte Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_SByte {[FieldOffset(0)] public sbyte Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_UShort {[FieldOffset(0)] public ushort Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Short {[FieldOffset(0)] public short Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_UInt {[FieldOffset(0)] public uint Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Int {[FieldOffset(0)] public int Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_ULong {[FieldOffset(0)] public ulong Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Long {[FieldOffset(0)] public long Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Float {[FieldOffset(0)] public float Value; } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit)] + public struct ValueStruct_Double {[FieldOffset(0)] public double Value; } + + [FlatBufferStruct] + public class GenericReferenceStruct where T : struct + { + [FlatBufferItem(0)] + public virtual T Item { get; set; } + } } public interface IValidStruct @@ -414,5 +727,29 @@ public struct ValidStruct_NotMarshallable_DueToSize : IValidStruct public long ID { get => this.D; set => this.D = value; } public ValidStruct_Inner IInner { get => this.Inner; set => this.Inner = value; } } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit, Size = 16)] + public struct SixteenByteStruct + { + [FieldOffset(0)] public byte A; + [FieldOffset(8)] public ulong B; + } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit, Size = 9)] + public struct NineByteStruct + { + [FieldOffset(0)] public ulong A; + [FieldOffset(8)] public byte B; + } + + [FlatBufferStruct, StructLayout(LayoutKind.Explicit, Size = 5)] + public struct FiveByteStruct + { + [FieldOffset(0)] public byte A; + [FieldOffset(1)] public byte B; + [FieldOffset(2)] public byte C; + [FieldOffset(3)] public byte D; + [FieldOffset(4)] public byte E; + } } }