diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorOfUnionTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorOfUnionTests.cs index a448f62c..ce0cb059 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorOfUnionTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorOfUnionTests.cs @@ -17,7 +17,7 @@ namespace FlatSharpEndToEndTests.ClassLib.FlatBufferVectorOfUnionTests; /// -/// Tests for the FlatBufferVector class that implements IList. +/// Tests for FlatSharp's IList of Union. /// public class FlatBufferVectorOfUnionTests diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorTests.cs index 23cab33f..428881e2 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/FlatBufferVectorTests.cs @@ -19,7 +19,7 @@ namespace FlatSharpEndToEndTests.ClassLib.FlatBufferVectorTests; /// -/// Tests for the FlatBufferVector class that implements IList. +/// Tests for FlatSharp's IList implementations. /// public class FlatBufferVectorTests { diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs new file mode 100644 index 00000000..290ca3b4 --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.cs @@ -0,0 +1,222 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using FlatSharp.Internal; +using System.IO; + +namespace FlatSharpEndToEndTests.ClassLib.SerializerConfigurationTests; + +public class SerializerConfigurationTests +{ + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UseDeserializationMode(FlatBufferDeserializationOption option) + { + var item = new Root { StringVector = new string[] { "a", "b", "c" } }; + + ISerializer serializer = Root.Serializer.WithSettings(opt => opt.UseDeserializationMode(option)); + + Root parsed = item.SerializeAndParse(serializer); + IFlatBufferDeserializedObject obj = parsed as IFlatBufferDeserializedObject; + Assert.Equal(option, obj.DeserializationContext.DeserializationOption); + } + + [Fact] + public void UseLazyDeserialization() + { + var item = new Root { StringVector = new string[] { "a", "b", "c" } }; + + ISerializer serializer = Root.Serializer.WithSettings(opt => opt.UseLazyDeserialization()); + + Root parsed = item.SerializeAndParse(serializer); + IFlatBufferDeserializedObject obj = parsed as IFlatBufferDeserializedObject; + Assert.Equal(FlatBufferDeserializationOption.Lazy, obj.DeserializationContext.DeserializationOption); + } + + [Fact] + public void UseProgressiveDeserialization() + { + var item = new Root { StringVector = new string[] { "a", "b", "c" } }; + + ISerializer serializer = Root.Serializer.WithSettings(opt => opt.UseProgressiveDeserialization()); + + Root parsed = item.SerializeAndParse(serializer); + IFlatBufferDeserializedObject obj = parsed as IFlatBufferDeserializedObject; + Assert.Equal(FlatBufferDeserializationOption.Progressive, obj.DeserializationContext.DeserializationOption); + } + + [Fact] + public void UseGreedyDeserialization() + { + var item = new Root { StringVector = new string[] { "a", "b", "c" } }; + + ISerializer serializer = Root.Serializer.WithSettings(opt => opt.UseGreedyDeserialization()); + + Root parsed = item.SerializeAndParse(serializer); + IFlatBufferDeserializedObject obj = parsed as IFlatBufferDeserializedObject; + Assert.Equal(FlatBufferDeserializationOption.Greedy, obj.DeserializationContext.DeserializationOption); + } + + [Fact] + public void UseGreedyMutableDeserialization() + { + var item = new Root { StringVector = new string[] { "a", "b", "c" } }; + + ISerializer serializer = Root.Serializer.WithSettings(opt => opt.UseGreedyMutableDeserialization()); + + Root parsed = item.SerializeAndParse(serializer); + IFlatBufferDeserializedObject obj = parsed as IFlatBufferDeserializedObject; + Assert.Equal(FlatBufferDeserializationOption.GreedyMutable, obj.DeserializationContext.DeserializationOption); + } + + [Theory] + [InlineData(FlatBufferDeserializationOption.Lazy, false, false)] + [InlineData(FlatBufferDeserializationOption.Lazy, true, true)] + [InlineData(FlatBufferDeserializationOption.Progressive, false, false)] + [InlineData(FlatBufferDeserializationOption.Progressive, true, true)] + [InlineData(FlatBufferDeserializationOption.Greedy, false, false)] + [InlineData(FlatBufferDeserializationOption.Greedy, true, false)] + [InlineData(FlatBufferDeserializationOption.GreedyMutable, false, false)] + [InlineData(FlatBufferDeserializationOption.GreedyMutable, true, false)] + public void UseMemoryCopySerialization(FlatBufferDeserializationOption option, bool enableMemCopy, bool expectMemCopy) + { + Root t = new() { StringVector = new[] { "A", "b", "c", } }; + + var compiled = Root.Serializer.WithSettings(s => s.UseMemoryCopySerialization(enableMemCopy).UseDeserializationMode(option)); + + // overallocate + byte[] data = new byte[1024]; + + int maxBytes = compiled.GetMaxSize(t); + Assert.Equal(88, maxBytes); + int actualBytes = compiled.Write(data, t); + Assert.Equal(58, actualBytes); + + // First test: Parse the array but don't trim the buffer. This causes the underlying + // buffer to be much larger than the actual data. + var parsed = compiled.Parse(data); + byte[] data2 = new byte[2048]; + int bytesWritten = compiled.Write(data2, parsed); + + if (expectMemCopy) + { + Assert.Equal(1024, bytesWritten); // We use mem copy serialization here, and we gave it 1024 bytes when we parsed. So we get 1024 bytes back out. + Assert.Equal(1024, compiled.GetMaxSize(parsed)); + Assert.Throws(() => compiled.Write(new byte[maxBytes], parsed)); + + // Repeat, but now using the trimmed array. + parsed = compiled.Parse(data.AsMemory().Slice(0, actualBytes)); + bytesWritten = compiled.Write(data2, parsed); + Assert.Equal(58, bytesWritten); + Assert.Equal(58, compiled.GetMaxSize(parsed)); + + // Default still returns 88. + Assert.Equal(88, Root.Serializer.GetMaxSize(parsed)); + } + else + { + Assert.Equal(58, bytesWritten); + Assert.Equal(88, compiled.GetMaxSize(parsed)); + compiled.Write(new byte[maxBytes], parsed); + } + } + + [Fact] + public void SharedStringWriters() + { + ISerializer noSharedStrings = Root.Serializer.WithSettings(opt => opt.DisableSharedStrings()); + ISerializer smallHashSharedStrings = Root.Serializer.WithSettings(opt => opt.UseDefaultSharedStringWriter(1)); + ISerializer largeHashSharedStrings = Root.Serializer.WithSettings(opt => opt.UseDefaultSharedStringWriter()); + ISerializer perfectSharedStrings = Root.Serializer.WithSettings(opt => opt.UseSharedStringWriter()); + + { + Root root = new Root { StringVector = new[] { "a", "b", "a", "b", "a", "b", "a", } }; + + byte[] notShared = root.AllocateAndSerialize(noSharedStrings); + byte[] smallShared = root.AllocateAndSerialize(smallHashSharedStrings); + byte[] largeShared = root.AllocateAndSerialize(largeHashSharedStrings); + byte[] perfectShared = root.AllocateAndSerialize(perfectSharedStrings); + + // Small shared doesn't accomplish anything since it alternates. + Assert.Equal(notShared.Length, smallShared.Length); + Assert.True(largeShared.Length < smallShared.Length); + Assert.Equal(largeShared.Length, perfectShared.Length); + } + + { + Root root = new Root { StringVector = new[] { "a", "a", "a", "a", "b", "b", "b", "b" } }; + + byte[] notShared = root.AllocateAndSerialize(noSharedStrings); + byte[] smallShared = root.AllocateAndSerialize(smallHashSharedStrings); + byte[] largeShared = root.AllocateAndSerialize(largeHashSharedStrings); + byte[] perfectShared = root.AllocateAndSerialize(perfectSharedStrings); + + // Small shared doesn't accomplish anything since it alternates. + Assert.True(smallShared.Length < notShared.Length); + Assert.Equal(largeShared.Length, smallShared.Length); + Assert.Equal(largeShared.Length, perfectShared.Length); + } + } + + private class PerfectSharedStringWriter : ISharedStringWriter + { + private readonly Dictionary> offsets = new(); + + public PerfectSharedStringWriter() + { + } + + public bool IsDirty => this.offsets.Count > 0; + + public void FlushWrites(TSpanWriter writer, Span data, SerializationContext context) where TSpanWriter : ISpanWriter + { + foreach (var kvp in this.offsets) + { + string value = kvp.Key; + + int stringOffset = writer.WriteAndProvisionString(data, value, context); + + for (int i = 0; i < kvp.Value.Count; ++i) + { + writer.WriteUOffset(data, kvp.Value[i], stringOffset); + } + } + + this.offsets.Clear(); + } + + public void Reset() + { + this.offsets.Clear(); + } + + public void WriteSharedString( + TSpanWriter spanWriter, + Span data, + int offset, + string value, + SerializationContext context) where TSpanWriter : ISpanWriter + { + if (!this.offsets.TryGetValue(value, out var list)) + { + list = new(); + this.offsets[value] = list; + } + + list.Add(offset); + } + } +} diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.fbs b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.fbs new file mode 100644 index 00000000..d2b7bbe9 --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/SerializerConfigurationTests.fbs @@ -0,0 +1,14 @@ + +attribute "fs_serializer"; +attribute "fs_unsafeStructVector"; +attribute "fs_valueStruct"; +attribute "fs_writeThrough"; +attribute "fs_vector"; +attribute "fs_sharedString"; + +namespace FlatSharpEndToEndTests.ClassLib.SerializerConfigurationTests; + +table Root (fs_serializer) +{ + StringVector : [ string ] (fs_sharedString); +} \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs b/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs index a67495a8..42936fb7 100644 --- a/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ClassLib/VTableTests.cs @@ -15,8 +15,9 @@ */ using FlatSharp.Internal; +using System.IO; -namespace FlatSharpEndToEndTests.VTables; +namespace FlatSharpEndToEndTests.ClassLib.VTables; public class VTableTests { @@ -43,6 +44,83 @@ public class VTableTests [Fact] public void Test_VTableGeneric() => this.RunTests(255, VTableGeneric.Create); + [Fact] + public void InitializeVTable_NormalTable() + { + byte[] buffer = + { + 8, 0, // vtable length + 12, 0, // table length + 4, 0, // index 0 offset + 8, 0, // index 1 offset + 8, 0, 0, 0, // soffset to vtable. + 1, 0, 0, 0, + 2, 0, 0, 0, + }; + + new ArrayInputBuffer(buffer).InitializeVTable( + 8, + out int vtableOffset, + out nuint fieldCount, + out ReadOnlySpan fieldData); + + Assert.Equal(0, vtableOffset); + Assert.Equal((nuint)2, fieldCount); + Assert.Equal(4, fieldData.Length); + } + + [Fact] + public void InitializeVTable_EmptyTable() + { + byte[] buffer = + { + 4, 0, // vtable length + 4, 0, // table length + 4, 0, 0, 0 // soffset to vtable. + }; + + new ArrayInputBuffer(buffer).InitializeVTable( + 4, + out int vtableOffset, + out nuint fieldCount, + out ReadOnlySpan fieldData); + + Assert.Equal(0, vtableOffset); + Assert.Equal((nuint)0, fieldCount); + Assert.Equal(0, fieldData.Length); + } + + [Fact] + public void InitializeVTable_InvalidLength() + { + byte[] buffer = + { + 3, 0, // vtable length + 4, 0, // table length + 4, 0, 0, 0 // soffset to vtable. + }; + + var ex = Assert.Throws(() => + new ArrayInputBuffer(buffer).InitializeVTable(4, out _, out _, out _)); + + Assert.Equal( + "FlatBuffer was in an invalid format: VTable was not long enough to be valid.", + ex.Message); + } + + [Fact] + public void ReadUOffset() + { + byte[] buffer = { 4, 0, 0, 0 }; + Assert.Equal(4, new ArrayInputBuffer(buffer).ReadUOffset(0)); + + buffer = new byte[] { 3, 0, 0, 0 }; + var ex = Assert.Throws(() => new ArrayInputBuffer(buffer).ReadUOffset(0)); + Assert.Equal( + "FlatBuffer was in an invalid format: Decoded uoffset_t had value less than 4. Value = 3", + ex.Message); + } + private void RunTests(int expectedMaxIndex, CreateCallback callback) where TVTable : struct, IVTable { diff --git a/src/Tests/FlatSharpEndToEndTests/Helpers.cs b/src/Tests/FlatSharpEndToEndTests/Helpers.cs index 737c29a6..e4aef176 100644 --- a/src/Tests/FlatSharpEndToEndTests/Helpers.cs +++ b/src/Tests/FlatSharpEndToEndTests/Helpers.cs @@ -31,17 +31,37 @@ public static IEnumerable AllOptions public static byte[] AllocateAndSerialize(this T item) where T : class, IFlatBufferSerializable { - byte[] data = new byte[item.Serializer.GetMaxSize(item)]; - int bytesWritten = item.Serializer.Write(data, item); + return item.AllocateAndSerialize(item.Serializer); + } + + public static byte[] AllocateAndSerialize(this T item, ISerializer serializer) where T : class, IFlatBufferSerializable + { + byte[] data = new byte[serializer.GetMaxSize(item)]; + int bytesWritten = serializer.Write(data, item); return data.AsMemory().Slice(0, bytesWritten).ToArray(); } + public static T SerializeAndParse(this T item) where T : class, IFlatBufferSerializable + { + return SerializeAndParse(item, item.Serializer); + } + public static T SerializeAndParse( this T item, - FlatBufferDeserializationOption? option = null) where T : class, IFlatBufferSerializable + FlatBufferDeserializationOption? option) where T : class, IFlatBufferSerializable { byte[] buffer = item.AllocateAndSerialize(); return item.Serializer.Parse(buffer, option); } + + public static T SerializeAndParse( + this T item, + ISerializer? serializer) where T : class, IFlatBufferSerializable + { + serializer ??= item.Serializer; + + byte[] buffer = item.AllocateAndSerialize(); + return serializer.Parse(buffer); + } } diff --git a/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs b/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs index 021902a8..a84fa153 100644 --- a/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/IO/InputBufferTests.cs @@ -130,83 +130,6 @@ public void ArraySegmentInputBuffer() (s, b) => s.Parse(new ArraySegment(b))); } - [Fact] - public void InitializeVTable_EmptyTable() - { - byte[] buffer = - { - 4, 0, // vtable length - 4, 0, // table length - 4, 0, 0, 0 // soffset to vtable. - }; - - new ArrayInputBuffer(buffer).InitializeVTable( - 4, - out int vtableOffset, - out nuint fieldCount, - out ReadOnlySpan fieldData); - - Assert.Equal(0, vtableOffset); - Assert.Equal((nuint)0, fieldCount); - Assert.Equal(0, fieldData.Length); - } - - [Fact] - public void InitializeVTable_InvalidLength() - { - byte[] buffer = - { - 3, 0, // vtable length - 4, 0, // table length - 4, 0, 0, 0 // soffset to vtable. - }; - - var ex = Assert.Throws(() => - new ArrayInputBuffer(buffer).InitializeVTable(4, out _, out _, out _)); - - Assert.Equal( - "FlatBuffer was in an invalid format: VTable was not long enough to be valid.", - ex.Message); - } - - [Fact] - public void InitializeVTable_NormalTable() - { - byte[] buffer = - { - 8, 0, // vtable length - 12, 0, // table length - 4, 0, // index 0 offset - 8, 0, // index 1 offset - 8, 0, 0, 0, // soffset to vtable. - 1, 0, 0, 0, - 2, 0, 0, 0, - }; - - new ArrayInputBuffer(buffer).InitializeVTable( - 8, - out int vtableOffset, - out nuint fieldCount, - out ReadOnlySpan fieldData); - - Assert.Equal(0, vtableOffset); - Assert.Equal((nuint)2, fieldCount); - Assert.Equal(4, fieldData.Length); - } - - [Fact] - public void ReadUOffset() - { - byte[] buffer = { 4, 0, 0, 0 }; - Assert.Equal(4, new ArrayInputBuffer(buffer).ReadUOffset(0)); - - buffer = new byte[] { 3, 0, 0, 0 }; - var ex = Assert.Throws(() => new ArrayInputBuffer(buffer).ReadUOffset(0)); - Assert.Equal( - "FlatBuffer was in an invalid format: Decoded uoffset_t had value less than 4. Value = 3", - ex.Message); - } - private void TableSerializationTest( ISpanWriter writer, Func, byte[], PrimitiveTypesTable> parse) diff --git a/src/Tests/FlatSharpEndToEndTests/RawData/RawData.fbs b/src/Tests/FlatSharpEndToEndTests/RawData/RawData.fbs new file mode 100644 index 00000000..1e69a712 --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/RawData/RawData.fbs @@ -0,0 +1,47 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +attribute "fs_vector"; +attribute "fs_serializer"; +attribute "fs_sortedVector"; +attribute "fs_sharedString"; + +namespace FlatSharpEndToEndTests.RawData; + +table SharedStringTable (fs_serializer) +{ + a : string (fs_sharedString); + b : string (fs_sharedString); + c : string (fs_sharedString); + vector : [ string ] (fs_sharedString); +} + +table EmptyTable (fs_serializer) { } + +struct SimpleStruct +{ + Long : long; + Byte : ubyte; + Uint : uint; +} + +table SimpleTable (fs_serializer) +{ + String : string; + struct : SimpleStruct; + struct_vector : [ SimpleStruct ]; + inner_table : SimpleTable; +} \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/RawData/RawDataSharedStringTests.cs b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataSharedStringTests.cs new file mode 100644 index 00000000..659db80d --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataSharedStringTests.cs @@ -0,0 +1,222 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharpEndToEndTests.RawData; + +public class RawDataSharedStringTests +{ + [Fact] + public void SharedStrings_Vector_Disabled() + { + var t = new SharedStringTable + { + Vector = new List { "string", "string", "string" }, + }; + + var noSharedStrings = SharedStringTable.Serializer.WithSettings(opt => opt.DisableSharedStrings()); + + byte[] buffer = t.AllocateAndSerialize(noSharedStrings); + + byte[] expectedBytes = new byte[] + { + 4, 0, 0, 0, // offset to table start + 248, 255, 255, 255, // soffset to vtable. + 16, 0, 0, 0, // uoffset to vector + 12, 0, 8, 0, // vtable length, table length + 0, 0, // vtable[0] + 0, 0, // vtable[1] + 0, 0, // vtable[2] + 4, 0, // vtable[3] + 3, 0, 0, 0, // vector length + 12, 0, 0, 0, // uffset to first item + 20, 0, 0, 0, // uoffset to second item + 28, 0, 0, 0, // uoffset to third item + 6, 0, 0, 0, // string length + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, 0, // null terminator + padding + 6, 0, 0, 0, + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, 0, // null terminator + padding + 6, 0, 0, 0, + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, // null terminator + }; + + Assert.True(expectedBytes.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void SharedStrings_Table_Disabled() + { + var t = new SharedStringTable + { + A = "string", + B = "string", + C = "string", + }; + + var noSharedStrings = SharedStringTable.Serializer.WithSettings(opt => opt.DisableSharedStrings()); + byte[] buffer = t.AllocateAndSerialize(noSharedStrings); + + byte[] expectedBytes = new byte[] + { + 4, 0, 0, 0, // offset to table start + 240, 255, 255, 255, // soffset to vtable. + 24, 0, 0, 0, // uoffset to string 1 + 32, 0, 0 ,0, // uoffset to string 2 + 40, 0, 0, 0, // uoffset to string 3 + 10, 0, 16, 0, // vtable length, table length + 12, 0, 8, 0, // vtable(2), vtable(1) + 4, 0, 0, 0, // vtable(1), padding + 6, 0, 0, 0, // string length + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, 0, // null terminator. + 6, 0, 0, 0, + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, 0, // null terminator. + 6, 0, 0, 0, + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, // null terminator. + }; + + Assert.True(expectedBytes.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void SharedStrings_Table() + { + var t = new SharedStringTable + { + A = "string", + B = "string", + C = "string", + }; + + byte[] buffer = t.AllocateAndSerialize(); + + byte[] expectedBytes = new byte[] + { + 4, 0, 0, 0, // offset to table start + 240, 255, 255, 255, // soffset to vtable. + 24, 0, 0, 0, // uoffset to string 1 + 20, 0, 0 ,0, // uoffset to string 2 + 16, 0, 0, 0, // uoffset to string 3 + 10, 0, 16, 0, // vtable length, table length + 12, 0, 8, 0, // vtable(2), vtable(1) + 4, 0, 0, 0, // vtable(1), padding + 6, 0, 0, 0, // string length + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, // null terminator. + }; + + Assert.True(expectedBytes.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void SharedStrings_Table_WithNull() + { + var t = new SharedStringTable + { + A = null, + B = "string", + C = null, + }; + + byte[] buffer = t.AllocateAndSerialize(); + + byte[] expectedBytes = new byte[] + { + 4, 0, 0, 0, // offset to table start + 248, 255, 255, 255, // soffset to vtable. + 12, 0, 0 ,0, // uoffset to string + 8, 0, 8, 0, // vtable length, table length + 0, 0, 4, 0, // vtable(0), vtable(1) + 6, 0, 0, 0, // string length + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0 // null terminator. + }; + + Assert.True(expectedBytes.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void SharedStrings_Table_WithEviction() + { + var t = new SharedStringTable + { + A = "string", + B = "foo", + C = "string", + }; + + var serializer = SharedStringTable.Serializer.WithSettings(s => s.UseDefaultSharedStringWriter(1)); + byte[] buffer = t.AllocateAndSerialize(serializer); + + byte[] expectedBytes = new byte[] + { + 4, 0, 0, 0, // offset to table start + 240, 255, 255, 255, // soffset to vtable. + 24, 0, 0, 0, // uoffset to string 1 + 32, 0, 0 ,0, // uoffset to string 2 + 36, 0, 0, 0, // uoffset to string 3 + 10, 0, 16, 0, // vtable length, table length + 12, 0, 8, 0, // vtable(0), vtable(1) + 4, 0, 0, 0, // vtable(2), padding + 6, 0, 0, 0, // string0 length + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, 0, // null terminator + 1 byte padding + 3, 0, 0, 0, // string1 length + (byte)'f', (byte)'o', (byte)'o', 0, // string1 + null terminator + 6, 0, 0, 0, + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0 // null terminator + }; + + Assert.True(expectedBytes.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void SharedStrings_Vector() + { + var t = new SharedStringTable + { + Vector = new List { "string", "string", "string" }, + }; + + byte[] buffer = t.AllocateAndSerialize(); + + byte[] expectedBytes = new byte[] + { + 4, 0, 0, 0, // offset to table start + 248, 255, 255, 255, // soffset to vtable. + 16, 0, 0, 0, // uoffset to vector + 12, 0, 8, 0, // vtable length, table length + 0, 0, // vtable[0] + 0, 0, // vtable[1] + 0, 0, // vtable[2] + 4, 0, // vtable[3] + 3, 0, 0, 0, // vector length + 12, 0, 0, 0, // uffset to first item + 8, 0, 0, 0, // uoffset to second item + 4, 0, 0, 0, // uoffset to third item + 6, 0, 0, 0, // string length + (byte)'s', (byte)'t', (byte)'r', (byte)'i', + (byte)'n', (byte)'g', 0, + }; + + Assert.True(expectedBytes.AsSpan().SequenceEqual(buffer)); + } +} \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/RawData/RawDataStringTests.cs b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataStringTests.cs new file mode 100644 index 00000000..3ddd7a06 --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataStringTests.cs @@ -0,0 +1,133 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharpEndToEndTests.RawData; + +using StringTable = FlatSharpEndToEndTests.TableMembers.StringTable; + +public class RawDataStringTests +{ + [Fact] + public void EmptyString() + { + var root = new StringTable + { + ItemStandard = string.Empty, + }; + + byte[] data = root.AllocateAndSerialize(); + + byte[] expectedResult = + { + 4, 0, 0, 0, // offset to table start + 248, 255, 255, 255, // soffset to vtable (-8) + 12, 0, 0, 0, // uoffset_t to string + 6, 0, // vtable length + 8, 0, // table length + 4, 0, // offset of index 0 field + 0, 0, // padding to 4-byte alignment + 0, 0, 0, 0, // vector length + 0, // null terminator (special case for strings). + }; + + Assert.True(expectedResult.AsSpan().SequenceEqual(data)); + } + + [Fact] + public void DeprecatedString() + { + var root = new StringTable + { + ItemDeprecated = string.Empty, + }; + + byte[] data = root.AllocateAndSerialize(); + + byte[] expectedResult = + { + 4, 0, 0, 0, // offset to table start + 252, 255, 255, 255, // soffset to vtable (-4) + 4, 0, // vtable length + 4, 0, // table length + }; + + Assert.True(expectedResult.AsSpan().SequenceEqual(data)); + } + + [Fact] + public void SimpleString() + { + var root = new StringTable + { + ItemStandard = new string(new char[] { (char)1, (char)2, (char)3 }), + }; + + byte[] target = root.AllocateAndSerialize(); + + byte[] expectedResult = + { + 4, 0, 0, 0, // offset to table start + 248, 255, 255, 255, // soffset to vtable (-8) + 12, 0, 0, 0, // uoffset_t to vector + 6, 0, // vtable length + 8, 0, // table length + 4, 0, // offset of index 0 field + 0, 0, // padding to 4-byte alignment + 3, 0, 0, 0, // vector length + 1, 2, 3, 0, // data + null terminator (special case for string vectors). + }; + + Assert.True(expectedResult.AsSpan().SequenceEqual(target)); + } + + [Fact] + public void StringVector() + { + var root = new StringTable + { + ItemDeprecated = "notnull", + ItemVectorImplicit = new[] { "abc_", "def", "ghi" } + }; + + byte[] data = root.AllocateAndSerialize(); + + byte[] expectedResult = + { + 4, 0, 0, 0, // offset to table start + 248, 255, 255, 255, // soffset to vtable (-8) + 16, 0, 0, 0, // uoffset_t to vector + 10, 0, // vtable length + 8, 0, // table length + 0, 0, // offset of index 0 field + 0, 0, // offset to index 1 field (deprecated) + 4, 0, // offset to index 2 field (the vector) + 0, 0, // padding to 4-byte alignment + 3, 0, 0, 0, // vector length + 12, 0, 0, 0, // offset to index 0 + 20, 0, 0, 0, // offset to index 1 + 24, 0, 0, 0, // offset to index 2 + 4, 0, 0 ,0, // length of string 0 + (byte)'a', (byte)'b', (byte)'c', (byte)'_', 0, // string 0 + 0, 0, 0, // padding + 3, 0, 0 ,0, // length of string 1 + (byte)'d', (byte)'e', (byte)'f', 0, // string 1 + 3, 0, 0 ,0, // length of string 1 + (byte)'g', (byte)'h', (byte)'i', 0, // string 2 + }; + + Assert.True(expectedResult.AsSpan().SequenceEqual(data)); + } +} \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs new file mode 100644 index 00000000..ad8bf60f --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/RawData/RawDataTableTests.cs @@ -0,0 +1,115 @@ +/* + * Copyright 2018 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace FlatSharpEndToEndTests.RawData; + +public class RawDataTableTests +{ + [Fact] + public void AllMembersNull() + { + SimpleTable table = new SimpleTable(); + byte[] buffer = table.AllocateAndSerialize(); + + byte[] expectedData = + { + 4, 0, 0, 0, + 252, 255, 255, 255, + 4, 0, + 4, 0, + }; + + Assert.True(expectedData.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void TableWithStruct() + { + SimpleTable table = new SimpleTable + { + Struct = new SimpleStruct { Byte = 1, Long = 2, Uint = 3 } + }; + + byte[] buffer = table.AllocateAndSerialize(); + + byte[] expectedData = + { + 4, 0, 0, 0, // uoffset to table start + 236, 255, 255, 255, // soffet to vtable (4 - x = 24 => x = -20) + 2, 0, 0, 0, 0, 0, 0, 0, // struct.long + 1, // struct.byte + 0, 0, 0, // padding + 3, 0, 0, 0, // struct.uint + 8, 0, // vtable length + 20, 0, // table length + 0, 0, // index 0 offset + 4, 0, // Index 1 offset + }; + + Assert.True(expectedData.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void TableWithStructAndString() + { + SimpleTable table = new SimpleTable + { + String = "hi", + Struct = new SimpleStruct { Byte = 1, Long = 2, Uint = 3 } + }; + + byte[] buffer = table.AllocateAndSerialize(); + + byte[] expectedData = + { + 4, 0, 0, 0, // uoffset to table start + 232, 255, 255, 255, // soffet to vtable (4 - x = 24 => x = -20) + 2, 0, 0, 0, 0, 0, 0, 0, // struct.long + 1, 0, 0, 0, // struct.byte + padding + 3, 0, 0, 0, // struct.uint + 12, 0, 0, 0, // uoffset to string + 8, 0, // vtable length + 24, 0, // table length + 20, 0, // index 0 offset + 4, 0, // Index 1 offset + 2, 0, 0, 0, // string length + 104, 105, 0, // hi + null terminator + }; + + Assert.True(expectedData.AsSpan().SequenceEqual(buffer)); + } + + [Fact] + public void EmptyTable_Serialize_MaxSize() + { + EmptyTable table = new EmptyTable(); + + byte[] buffer = table.AllocateAndSerialize(); + + byte[] expectedData = + { + 4, 0, 0, 0, + 252, 255, 255, 255, + 4, 0, + 4, 0, + }; + + Assert.True(expectedData.AsSpan().SequenceEqual(buffer)); + + int maxSize = EmptyTable.Serializer.GetMaxSize(table); + Assert.Equal(23, maxSize); + } +} \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembers.fbs b/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembers.fbs index 6116fda9..56ee4981 100644 --- a/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembers.fbs +++ b/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembers.fbs @@ -143,6 +143,14 @@ table DoubleTable (fs_serializer) item_readonly_list : [ double ] (fs_vector:"IReadOnlyList"); } +table StringTable (fs_serializer) +{ + item_standard : string; + item_deprecated : string (deprecated); + item_vector_implicit : [ string ]; + item_vector_readonly : [ string ] (fs_vector:"IReadOnlyList"); +} + table EmptyTable (fs_serializer) { } \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs b/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs index 76c363b7..dd9e352b 100644 --- a/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/TableMembers/TableMembersTests.cs @@ -83,6 +83,47 @@ public void Byte(FlatBufferDeserializationOption option) [ClassData(typeof(DeserializationOptionClassData))] public void Double(FlatBufferDeserializationOption option) => this.RunTest(3.14, option); + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void String(FlatBufferDeserializationOption option) + { + { + byte[] emptyTable = new EmptyTable().AllocateAndSerialize(); + StringTable parsed = StringTable.Serializer.Parse(emptyTable, option); + + Assert.Null(parsed.ItemDeprecated); + Assert.Null(parsed.ItemStandard); + Assert.Null(parsed.ItemVectorImplicit); + Assert.Null(parsed.ItemVectorReadonly); + } + + { + StringTable template = new() + { + ItemDeprecated = "deprecated", + ItemStandard = "standard", + ItemVectorImplicit = new[] { "a", "b", }, + ItemVectorReadonly = new[] { "c", "d", "e" }, + }; + + StringTable parsed = template.SerializeAndParse(option); + + Assert.Null(parsed.ItemDeprecated); + Assert.Equal("standard", parsed.ItemStandard); + + IList @implicit = parsed.ItemVectorImplicit; + Assert.Equal(2, @implicit.Count); + Assert.Equal("a", @implicit[0]); + Assert.Equal("b", @implicit[1]); + + IReadOnlyList rolist = parsed.ItemVectorReadonly; + Assert.Equal(3, rolist.Count); + Assert.Equal("c", rolist[0]); + Assert.Equal("d", rolist[1]); + Assert.Equal("e", rolist[2]); + } + } + private void RunTest(T expectedDefault, FlatBufferDeserializationOption option) where T : struct where TTable : class, ITypedTable, IFlatBufferSerializable, new() diff --git a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs index 1c121e05..e0f0fd85 100644 --- a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs @@ -74,7 +74,7 @@ public void WriteThrough_ValueStruct_InTable() [Fact] public void Basics() { - Assert.Equal(148, Unsafe.SizeOf()); + Assert.Equal(36, Unsafe.SizeOf()); } [Fact] @@ -82,7 +82,7 @@ public void ExtensionMethod_WorksForVectors() { ValueStruct v = default; - Assert.Equal(128, v.D_Length); + Assert.Equal(16, v.D_Length); for (int i = 0; i < v.D_Length; ++i) { diff --git a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructs.fbs b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructs.fbs index ef068d59..771e58e4 100644 --- a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructs.fbs +++ b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructs.fbs @@ -39,7 +39,7 @@ struct ValueStruct (fs_valueStruct) { A : ubyte; B : int; C : long; - D : [ubyte : 128]; + D : [ ubyte : 16 ]; Inner : InnerValueStruct; } @@ -50,36 +50,36 @@ struct RefStruct { struct ValueUnsafeStructVector_Byte (fs_valueStruct) { GuardLower : ulong; - Vector : [ubyte : 128] (fs_unsafeStructVector); + Vector : [ubyte : 16] (fs_unsafeStructVector); GuardHigher : ulong; } struct ValueUnsafeStructVector_UShort (fs_valueStruct) { GuardLower : ulong; - Vector : [ushort : 128] (fs_unsafeStructVector); + Vector : [ushort : 16] (fs_unsafeStructVector); GuardHigher : ulong; } struct ValueUnsafeStructVector_UInt (fs_valueStruct) { GuardLower : ulong; - Vector : [uint : 128] (fs_unsafeStructVector); + Vector : [uint : 16] (fs_unsafeStructVector); GuardHigher : ulong; } struct ValueUnsafeStructVector_ULong (fs_valueStruct) { GuardLower : ulong; - Vector : [ulong : 128] (fs_unsafeStructVector); + Vector : [ulong : 16] (fs_unsafeStructVector); GuardHigher : ulong; } struct ValueUnsafeStructVector_Float (fs_valueStruct) { GuardLower : ulong; - Vector : [float : 128] (fs_unsafeStructVector); + Vector : [float : 16] (fs_unsafeStructVector); GuardHigher : ulong; } struct ValueUnsafeStructVector_Double (fs_valueStruct) { GuardLower : ulong; - Vector : [double : 128] (fs_unsafeStructVector); + Vector : [double : 16] (fs_unsafeStructVector); GuardHigher : ulong; } \ No newline at end of file diff --git a/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/IndexedVectorTests.cs b/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/IndexedVectorTests.cs new file mode 100644 index 00000000..ffebf2ff --- /dev/null +++ b/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/IndexedVectorTests.cs @@ -0,0 +1,210 @@ +/* + * Copyright 2021 James Courtney + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using FlatSharp.Internal; +using System.Runtime.InteropServices; + +namespace FlatSharpEndToEndTests.Vectors.Sorted; + +public class IndexedVectorTests +{ + private static readonly Random Rng = new(); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Bool(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfBool, + (x, v) => x.IndexedVectorOfBool = v, + () => new BoolKey { Key = Rng.Next() % 2 == 0 }, + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Byte(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfByte, + (x, v) => x.IndexedVectorOfByte = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void SByte(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfSbyte, + (x, v) => x.IndexedVectorOfSbyte = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Short(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfShort, + (x, v) => x.IndexedVectorOfShort = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UShort(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfUshort, + (x, v) => x.IndexedVectorOfUshort = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Int(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfInt, + (x, v) => x.IndexedVectorOfInt = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UInt(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfUint, + (x, v) => x.IndexedVectorOfUint = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Long(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfLong, + (x, v) => x.IndexedVectorOfLong = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void ULong(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfUlong, + (x, v) => x.IndexedVectorOfUlong = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Float(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfFloat, + (x, v) => x.IndexedVectorOfFloat = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Double(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfDouble, + (x, v) => x.IndexedVectorOfDouble = v, + GetValueFactory((k, v) => k.Key = v), + k => k.Key); + + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void String(FlatBufferDeserializationOption opt) => this.IndexedVectorTest( + opt, + x => x.IndexedVectorOfString, + (x, v) => x.IndexedVectorOfString = v, + () => + { + int length = Rng.Next(0, 2048); + byte[] data = new byte[length]; + Rng.NextBytes(data); + return new StringKey { Key = Convert.ToBase64String(data) }; + }, + k => k.Key); + + private static Func GetValueFactory(Action setKey) + where TKey : struct + where TValue : class, ISortableTable, new() + { + return () => + { + byte[] data = new byte[8]; + Rng.NextBytes(data); + TKey key = MemoryMarshal.Cast(data.AsSpan())[0]; + var val = new TValue(); + setKey(val, key); + return val; + }; + } + + private void IndexedVectorTest( + FlatBufferDeserializationOption option, + Func> getVector, + Action> setVector, + Func createValue, + Func getKey) + where TValue : class, ISortableTable + { + RootTableIndexed root = new(); + + HashSet knownKeys = new(); + + IIndexedVector originalVector = new IndexedVector(); + for (int i = 0; i < 100; ++i) + { + var toAdd = createValue(); + + // Some key types may have a small range of values + // such as bool and byte. + if (knownKeys.Add(getKey(toAdd))) + { + originalVector.Add(toAdd); + } + } + + setVector(root, originalVector); + + byte[] data = root.AllocateAndSerialize(); + var parsed = RootTableIndexed.Serializer.Parse(data, option); + + IIndexedVector vector = getVector(parsed); + + Assert.Equal(knownKeys.Count, vector.Count); + + foreach (var key in knownKeys) + { + Assert.True(vector.TryGetValue(key, out TValue value)); + Assert.True(vector.ContainsKey(key)); + Assert.NotNull(vector[key]); + Assert.NotNull(value); + Assert.Equal(key, getKey(value)); + } + + for (int i = 0; i < 100; ++i) + { + TKey key = getKey(createValue()); + if (!knownKeys.Contains(key)) + { + Assert.False(vector.TryGetValue(key, out TValue value)); + Assert.Null(value); + Assert.False(vector.ContainsKey(key)); + Assert.Throws(() => vector[key]); + } + } + } +} diff --git a/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectorTests.cs b/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectorTests.cs index 57649c5b..c7412dd5 100644 --- a/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectorTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectorTests.cs @@ -21,8 +21,10 @@ namespace FlatSharpEndToEndTests.Vectors.Sorted; public class SortedVectorTests { - [Fact] - public void Bool() => this.SortedVectorTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Bool(FlatBufferDeserializationOption opt) => this.SortedVectorTest( + opt, rng => rng.Next() % 2 == 0, rt => rt.ListVectorOfBool, (rt, l) => rt.ListVectorOfBool = l, @@ -31,78 +33,100 @@ public void Bool() => this.SortedVectorTest( (k, v) => k.Key = v, Comparer.Default); - [Fact] - public void Byte() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Byte(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfByte, (rt, l) => rt.ListVectorOfByte = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void SByte() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void SByte(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfSbyte, (rt, l) => rt.ListVectorOfSbyte = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Short() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Short(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfShort, (rt, l) => rt.ListVectorOfShort = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void UShort() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UShort(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfUshort, (rt, l) => rt.ListVectorOfUshort = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Int() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Int(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfInt, (rt, l) => rt.ListVectorOfInt = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void UInt() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UInt(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfUint, (rt, l) => rt.ListVectorOfUint = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Long() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Long(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfLong, (rt, l) => rt.ListVectorOfLong = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void ULong() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void ULong(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfUlong, (rt, l) => rt.ListVectorOfUlong = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Float() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Float(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfFloat, (rt, l) => rt.ListVectorOfFloat = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Double() => this.SortedVectorStructTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Double(FlatBufferDeserializationOption opt) => this.SortedVectorStructTest( + opt, rt => rt.ListVectorOfDouble, (rt, l) => rt.ListVectorOfDouble = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void String_Base64() => this.SortedVectorTest( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void String_Base64(FlatBufferDeserializationOption opt) => this.SortedVectorTest( + opt, rng => { int length = rng.Next(0, 2048); @@ -117,8 +141,10 @@ public void String_Base64() => this.SortedVectorTest( (k, v) => k.Key = v, new Utf8StringComparer()); - [Fact] - public void Bool_ReadOnly() => this.SortedVectorTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Bool_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorTestReadOnly( + opt, rng => rng.Next() % 2 == 0, rt => rt.ListVectorOfBool, (rt, l) => rt.ListVectorOfBool = l, @@ -127,78 +153,100 @@ public void Bool_ReadOnly() => this.SortedVectorTestReadOnly( (k, v) => k.Key = v, Comparer.Default); - [Fact] - public void Byte_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Byte_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfByte, (rt, l) => rt.ListVectorOfByte = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void SByte_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void SByte_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfSbyte, (rt, l) => rt.ListVectorOfSbyte = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Short_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Short_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfShort, (rt, l) => rt.ListVectorOfShort = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void UShort_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UShort_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfUshort, (rt, l) => rt.ListVectorOfUshort = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Int_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Int_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfInt, (rt, l) => rt.ListVectorOfInt = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void UInt_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void UInt_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfUint, (rt, l) => rt.ListVectorOfUint = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Long_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Long_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfLong, (rt, l) => rt.ListVectorOfLong = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void ULong_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void ULong_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfUlong, (rt, l) => rt.ListVectorOfUlong = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Float_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Float_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfFloat, (rt, l) => rt.ListVectorOfFloat = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void Double_ReadOnly() => this.SortedVectorStructTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void Double_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorStructTestReadOnly( + opt, rt => rt.ListVectorOfDouble, (rt, l) => rt.ListVectorOfDouble = l, k => k.Key, (k, v) => k.Key = v); - [Fact] - public void String_Base64_ReadOnly() => this.SortedVectorTestReadOnly( + [Theory] + [ClassData(typeof(DeserializationOptionClassData))] + public void String_Base64_ReadOnly(FlatBufferDeserializationOption opt) => this.SortedVectorTestReadOnly( + opt, rng => { int length = rng.Next(0, 2048); @@ -214,6 +262,7 @@ public void String_Base64_ReadOnly() => this.SortedVectorTestReadOnly( + FlatBufferDeserializationOption option, Func> getList, Action> setList, Func getKey, @@ -223,6 +272,7 @@ private void SortedVectorStructTestReadOnly( where TKey : struct { this.SortedVectorTestReadOnly( + option, rng => { byte[] data = new byte[8]; @@ -239,6 +289,7 @@ private void SortedVectorStructTestReadOnly( } private void SortedVectorStructTest( + FlatBufferDeserializationOption option, Func> getList, Action> setList, Func getKey, @@ -248,6 +299,7 @@ private void SortedVectorStructTest( where TKey : struct { this.SortedVectorTest( + option, rng => { byte[] data = new byte[8]; @@ -264,6 +316,7 @@ private void SortedVectorStructTest( } private void SortedVectorTestReadOnly( + FlatBufferDeserializationOption opt, Func createKey, Func> getList, Action> setList, @@ -290,11 +343,12 @@ private void SortedVectorTestReadOnly( list.Add(value); } - this.SortedVectorTestReadOnly(table, getList, getKey, comparer); + this.SortedVectorTestReadOnly(opt, table, getList, getKey, comparer); } } private void SortedVectorTest( + FlatBufferDeserializationOption option, Func createKey, Func> getList, Action> setList, @@ -321,11 +375,12 @@ private void SortedVectorTest( list.Add(value); } - this.SortedVectorTest(table, getList, getKey, comparer); + this.SortedVectorTest(option, table, getList, getKey, comparer); } } private void SortedVectorTestReadOnly( + FlatBufferDeserializationOption option, RootTableReadOnly root, Func> getList, Func getKey, @@ -335,42 +390,34 @@ private void SortedVectorTestReadOnly( IReadOnlyList rootList = getList(root); byte[] data = root.AllocateAndSerialize(); - void RunTest( - FlatBufferDeserializationOption option) - { - var parsed = RootTableReadOnly.Serializer.Parse(data, option); + var parsed = RootTableReadOnly.Serializer.Parse(data, option); + + IReadOnlyList vector = getList(parsed); - IReadOnlyList vector = getList(parsed); + Assert.Equal(rootList.Count, vector.Count); - Assert.Equal(rootList.Count, vector.Count); + if (rootList.Count > 0) + { + TValue previous = vector[0]; - if (rootList.Count > 0) + for (int i = 0; i < rootList.Count; ++i) { - TValue previous = vector[0]; - - for (int i = 0; i < rootList.Count; ++i) - { - var item = vector[i]; - Assert.True(comparer.Compare(getKey(previous), getKey(item)) <= 0); - previous = item; - } - - foreach (var originalItem in rootList) - { - var item = vector.BinarySearchByFlatBufferKey(getKey(originalItem)); - Assert.NotNull(item); - Assert.Equal(getKey(originalItem).ToString(), getKey(item).ToString()); - } + var item = vector[i]; + Assert.True(comparer.Compare(getKey(previous), getKey(item)) <= 0); + previous = item; } - } - foreach (FlatBufferDeserializationOption mode in Enum.GetValues(typeof(FlatBufferDeserializationOption))) - { - RunTest(mode); + foreach (var originalItem in rootList) + { + var item = vector.BinarySearchByFlatBufferKey(getKey(originalItem)); + Assert.NotNull(item); + Assert.Equal(getKey(originalItem).ToString(), getKey(item).ToString()); + } } } private void SortedVectorTest( + FlatBufferDeserializationOption option, RootTable root, Func> getList, Func getKey, @@ -380,38 +427,29 @@ private void SortedVectorTest( IList rootList = getList(root); byte[] data = root.AllocateAndSerialize(); - void RunTest( - FlatBufferDeserializationOption option) - { - var parsed = RootTable.Serializer.Parse(data, option); + var parsed = RootTable.Serializer.Parse(data, option); + + IList vector = getList(parsed); - IList vector = getList(parsed); + Assert.Equal(rootList.Count, vector.Count); - Assert.Equal(rootList.Count, vector.Count); + if (rootList.Count > 0) + { + TValue previous = vector[0]; - if (rootList.Count > 0) + for (int i = 0; i < rootList.Count; ++i) { - TValue previous = vector[0]; - - for (int i = 0; i < rootList.Count; ++i) - { - var item = vector[i]; - Assert.True(comparer.Compare(getKey(previous), getKey(item)) <= 0); - previous = item; - } - - foreach (var originalItem in rootList) - { - var item = vector.BinarySearchByFlatBufferKey(getKey(originalItem)); - Assert.NotNull(item); - Assert.Equal(getKey(originalItem).ToString(), getKey(item).ToString()); - } + var item = vector[i]; + Assert.True(comparer.Compare(getKey(previous), getKey(item)) <= 0); + previous = item; } - } - foreach (FlatBufferDeserializationOption mode in Enum.GetValues(typeof(FlatBufferDeserializationOption))) - { - RunTest(mode); + foreach (var originalItem in rootList) + { + var item = vector.BinarySearchByFlatBufferKey(getKey(originalItem)); + Assert.NotNull(item); + Assert.Equal(getKey(originalItem).ToString(), getKey(item).ToString()); + } } } } diff --git a/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectors.fbs b/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectors.fbs index 957aa3ae..e1cd1c59 100644 --- a/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectors.fbs +++ b/src/Tests/FlatSharpEndToEndTests/Vectors/Sorted/SortedVectors.fbs @@ -52,6 +52,28 @@ table RootTableReadOnly (fs_serializer) list_vector_of_string : [ StringKey ] (fs_sortedVector, fs_vector:"IReadOnlyList"); } +table RootTableIndexed (fs_serializer) +{ + indexed_vector_of_bool : [ BoolKey ] (fs_vector:"IIndexedVector"); + + indexed_vector_of_byte : [ ByteKey ] (fs_vector:"IIndexedVector"); + indexed_vector_of_sbyte : [ SByteKey ] (fs_vector:"IIndexedVector"); + + indexed_vector_of_short : [ ShortKey ] (fs_vector:"IIndexedVector"); + indexed_vector_of_ushort : [ UShortKey ] (fs_vector:"IIndexedVector"); + + indexed_vector_of_int : [ IntKey ] (fs_vector:"IIndexedVector"); + indexed_vector_of_uint : [ UIntKey ] (fs_vector:"IIndexedVector"); + + indexed_vector_of_long : [ LongKey ] (fs_vector:"IIndexedVector"); + indexed_vector_of_ulong : [ ULongKey ] (fs_vector:"IIndexedVector"); + + indexed_vector_of_float : [ FloatKey ] (fs_vector:"IIndexedVector"); + indexed_vector_of_double : [ DoubleKey ] (fs_vector:"IIndexedVector"); + + indexed_vector_of_string : [ StringKey ] (fs_vector:"IIndexedVector"); +} + table BoolKey { Key : bool (key); } table ByteKey { Key : ubyte (key); } table SByteKey { Key : byte (key); } diff --git a/src/Tests/FlatSharpTests/SerializationTests/MemCopySerializeTests.cs b/src/Tests/FlatSharpTests/SerializationTests/MemCopySerializeTests.cs deleted file mode 100644 index ce17a201..00000000 --- a/src/Tests/FlatSharpTests/SerializationTests/MemCopySerializeTests.cs +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2021 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using FlatSharp; - -namespace FlatSharpTests; - -/// -/// Tests serialization using the memory copy shortcut. -/// -public class MemCopySerializeTests -{ - [Fact] - public void GreedyMemoryCopySerialization_NoEffect() - { - TestTable t = new() - { - Foo = "foobar", - Struct = new Struct - { - Bar = 12, - } - }; - - var compiled = FlatBufferSerializer.Default.Compile>().WithSettings(s => s.UseMemoryCopySerialization()); - - byte[] data = new byte[1024]; - Assert.Equal(70, compiled.GetMaxSize(t)); - int actualBytes = compiled.Write(data, t); - - // First test: Parse the array but don't trim the buffer. This causes the underlying - // buffer to be much larger than the actual data. - var parsed = compiled.Parse(data); - byte[] data2 = new byte[2048]; - int bytesWritten = compiled.Write(data2, parsed); - - Assert.Equal(35, actualBytes); - Assert.Equal(35, bytesWritten); - Assert.Equal(70, compiled.GetMaxSize(parsed)); - } - - [Fact] - public void LazyMemoryCopySerialization() - { - TestTable t = new() - { - Foo = "foobar", - Struct = new WriteThroughStruct - { - Bar = 12, - } - }; - - FlatBufferSerializer serializer = FlatBufferSerializer.Default; - var compiled = serializer.Compile>().WithSettings(s => s.UseMemoryCopySerialization().UseLazyDeserialization()); - - byte[] data = new byte[1024]; - Assert.Equal(70, compiled.GetMaxSize(t)); - int actualBytes = compiled.Write(data, t); - - // First test: Parse the array but don't trim the buffer. This causes the underlying - // buffer to be much larger than the actual data. - var parsed = compiled.Parse(data); - byte[] data2 = new byte[2048]; - int bytesWritten = compiled.Write(data2, parsed); - - Assert.Equal(35, actualBytes); - Assert.Equal(1024, bytesWritten); // We use mem copy serialization here, and we gave it 1024 bytes when we parsed. So we get 1024 bytes back out. - Assert.Equal(1024, compiled.GetMaxSize(parsed)); - Assert.Throws(() => compiled.Write(new byte[35], parsed)); - - // Repeat, but now using the trimmed array. - parsed = compiled.Parse(data.AsMemory().Slice(0, actualBytes)); - bytesWritten = compiled.Write(data2, parsed); - Assert.Equal(35, actualBytes); - Assert.Equal(35, bytesWritten); - Assert.Equal(35, compiled.GetMaxSize(parsed)); - - // Default still returns 70. - Assert.Equal(70, serializer.GetMaxSize(parsed)); - } - - [Fact] - public void ProgressiveMemoryCopySerialization() - { - TestTable t = new() - { - Foo = "foobar", - Struct = new Struct - { - Bar = 12, - } - }; - - FlatBufferSerializer serializer = FlatBufferSerializer.Default; - var compiled = serializer.Compile>().WithSettings(s => s.UseMemoryCopySerialization().UseProgressiveDeserialization()); - - byte[] data = new byte[1024]; - Assert.Equal(70, compiled.GetMaxSize(t)); - int actualBytes = compiled.Write(data, t); - - // First test: Parse the array but don't trim the buffer. This causes the underlying - // buffer to be much larger than the actual data. - var parsed = compiled.Parse(data); - byte[] data2 = new byte[2048]; - int bytesWritten = compiled.Write(data2, parsed); - - Assert.Equal(35, actualBytes); - Assert.Equal(1024, bytesWritten); // We use mem copy serialization here, and we gave it 1024 bytes when we parsed. So we get 1024 bytes back out. - Assert.Equal(1024, compiled.GetMaxSize(parsed)); - Assert.Throws(() => compiled.Write(new byte[35], parsed)); - - // Repeat, but now using the trimmed array. - parsed = compiled.Parse(data.AsMemory().Slice(0, actualBytes)); - bytesWritten = compiled.Write(data2, parsed); - Assert.Equal(35, actualBytes); - Assert.Equal(35, bytesWritten); - Assert.Equal(35, compiled.GetMaxSize(parsed)); - - // Default still returns 70. - Assert.Equal(70, serializer.GetMaxSize(parsed)); - } - - [FlatBufferTable] - public class TestTable where T : IInt - { - [FlatBufferItem(0)] - public virtual string? Foo { get; set; } - - [FlatBufferItem(1)] - public virtual T? Struct { get; set; } - } - - public interface IInt - { - int Bar { get; set; } - } - - [FlatBufferStruct] - public class WriteThroughStruct : IInt - { - [FlatBufferItem(0, WriteThrough = true)] - public virtual int Bar { get; set; } - } - - [FlatBufferStruct] - public class Struct : IInt - { - [FlatBufferItem(0)] - public virtual int Bar { get; set; } - } -} diff --git a/src/Tests/FlatSharpTests/SerializationTests/SharedStringTests.cs b/src/Tests/FlatSharpTests/SerializationTests/SharedStringTests.cs deleted file mode 100644 index d928e7cf..00000000 --- a/src/Tests/FlatSharpTests/SerializationTests/SharedStringTests.cs +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2020 James Courtney - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System.Text; - -namespace FlatSharpTests; - -/// -/// Tests default values on a table. -/// -public class SharedStringTests -{ - [Fact] - public void Test_NonSharedStringVector() - { - var t = new RegularStringsVector - { - StringVector = new List { "string", "string", "string" }, - }; - - byte[] destination = new byte[1024]; - - var serializer = FlatBufferSerializer.Default.Compile(); - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 248, 255, 255, 255, // soffset to vtable. - 12, 0, 0, 0, // uoffset to vector - 6, 0, 8, 0, // vtable length, table length - 4, 0, 0, 0, // offset within table to item 0 + alignment - 3, 0, 0, 0, // vector length - 12, 0, 0, 0, // uffset to first item - 20, 0, 0, 0, // uoffset to second item - 28, 0, 0, 0, // uoffset to third item - 6, 0, 0, 0, // string length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, 0, // null terminator + padding - 6, 0, 0, 0, - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, 0, // null terminator + padding - 6, 0, 0, 0, - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, // null terminator - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - } - - [Fact] - public void Test_TableNonSharedStrings() - { - var t = new RegularStringsTable - { - String1 = "string", - String2 = "string", - String3 = "string", - }; - - byte[] destination = new byte[1024]; - - var serializer = FlatBufferSerializer.Default.Compile(); - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 240, 255, 255, 255, // soffset to vtable. - 24, 0, 0, 0, // uoffset to string 1 - 32, 0, 0 ,0, // uoffset to string 2 - 40, 0, 0, 0, // uoffset to string 3 - 10, 0, 16, 0, // vtable length, table length - 12, 0, 8, 0, // vtable(2), vtable(1) - 4, 0, 0, 0, // vtable(1), padding - 6, 0, 0, 0, // string length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, 0, // null terminator. - 6, 0, 0, 0, - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, 0, // null terminator. - 6, 0, 0, 0, - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, // null terminator. - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - } - - [Fact] - public void Test_VectorSharedStrings() - { - var t = new SharedStringsVector - { - StringVector = new List { "string", "string", "string" }, - }; - - byte[] destination = new byte[1024]; - - int maxBytes = FlatBufferSerializer.Default.GetMaxSize(t); - var serializer = FlatBufferSerializer.Default.Compile(); - - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - Assert.True(bytesWritten <= maxBytes); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 248, 255, 255, 255, // soffset to vtable. - 12, 0, 0, 0, // uoffset to vector - 6, 0, 8, 0, // vtable length, table length - 4, 0, 0, 0, // offset within table to item 0 + alignment - 3, 0, 0, 0, // vector length - 12, 0, 0, 0, // uffset to first item - 8, 0, 0, 0, // uoffset to second item - 4, 0, 0, 0, // uoffset to third item - 6, 0, 0, 0, // string length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0 // null terminator. - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - } - - [Fact] - public void Test_ISerializerSharedStringSettings() - { - var t = new SharedStringsVector - { - StringVector = new List { "string", "string", "string" }, - }; - - byte[] destination = new byte[1024]; - - int maxBytes = FlatBufferSerializer.Default.GetMaxSize(t); - var serializer = FlatBufferSerializer.Default.Compile(); - - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - Assert.True(bytesWritten <= maxBytes); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 248, 255, 255, 255, // soffset to vtable. - 12, 0, 0, 0, // uoffset to vector - 6, 0, 8, 0, // vtable length, table length - 4, 0, 0, 0, // offset within table to item 0 + alignment - 3, 0, 0, 0, // vector length - 12, 0, 0, 0, // uffset to first item - 8, 0, 0, 0, // uoffset to second item - 4, 0, 0, 0, // uoffset to third item - 6, 0, 0, 0, // string length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0 // null terminator. - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - - // Set SharedStringWriter to null. Shared strings remain turned on. - serializer = serializer.WithSettings(s => s.UseSharedStringWriter()); - bytesWritten = serializer.Write(default(SpanWriter), destination, t); - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - - // Set SharedStringWriter to return null. Shared strings turn off. - serializer = serializer.WithSettings(s => s.DisableSharedStrings()); - int bytesWrittenBig = serializer.Write(default(SpanWriter), destination, t); - Assert.True(bytesWrittenBig > bytesWritten); - } - - [Fact] - public void Test_TableSharedStrings() - { - var t = new SharedStringsTable - { - String1 = "string", - String2 = "string", - String3 = "string", - }; - - byte[] destination = new byte[1024]; - - var serializer = FlatBufferSerializer.Default.Compile(); - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 240, 255, 255, 255, // soffset to vtable. - 24, 0, 0, 0, // uoffset to string 1 - 20, 0, 0 ,0, // uoffset to string 2 - 16, 0, 0, 0, // uoffset to string 3 - 10, 0, 16, 0, // vtable length, table length - 12, 0, 8, 0, // vtable(0), vtable(1) - 4, 0, 0, 0, // vtable(2), padding - 6, 0, 0, 0, // string length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', (byte)'n', (byte)'g', - 0 // null terminator. - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - } - - [Fact] - public void Test_TableSharedStringsWithNull() - { - var t = new SharedStringsTable - { - String1 = null, - String2 = "string", - String3 = (string)null, - }; - - byte[] destination = new byte[1024]; - - var serializer = FlatBufferSerializer.Default.Compile(); - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 248, 255, 255, 255, // soffset to vtable. - 12, 0, 0 ,0, // uoffset to string - 8, 0, 8, 0, // vtable length, table length - 0, 0, 4, 0, // vtable(0), vtable(1) - 6, 0, 0, 0, // string length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0 // null terminator. - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - } - - /// - /// Tests with an LRU string writer that can only hold onto one item at a time. Each new string it sees evicts the old one. - /// - [Fact] - public void Test_TableSharedStringsWithEviction() - { - var t = new SharedStringsTable - { - String1 = "string", - String2 = "foo", - String3 = "string", - }; - - byte[] destination = new byte[1024]; - var serializer = FlatBufferSerializer.Default.Compile() - .WithSettings(s => s.UseDefaultSharedStringWriter(1)); - - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - byte[] expectedBytes = new byte[] - { - 4, 0, 0, 0, // offset to table start - 240, 255, 255, 255, // soffset to vtable. - 24, 0, 0, 0, // uoffset to string 1 - 32, 0, 0 ,0, // uoffset to string 2 - 36, 0, 0, 0, // uoffset to string 3 - 10, 0, 16, 0, // vtable length, table length - 12, 0, 8, 0, // vtable(0), vtable(1) - 4, 0, 0, 0, // vtable(2), padding - 6, 0, 0, 0, // string0 length - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0, 0, // null terminator + 1 byte padding - 3, 0, 0, 0, // string1 length - (byte)'f', (byte)'o', (byte)'o', 0, // string1 + null terminator - 6, 0, 0, 0, - (byte)'s', (byte)'t', (byte)'r', (byte)'i', - (byte)'n', (byte)'g', 0 // null terminator - }; - - Assert.True(expectedBytes.AsSpan().SequenceEqual(destination.AsSpan().Slice(0, bytesWritten))); - } - - /// - /// Identical to the above test but with a large enough LRU cache to handle both strings. - /// - [Fact] - public void Test_TableSharedStringsWithoutEviction() - { - var t = new SharedStringsTable - { - String1 = "string", - String2 = "foo", - String3 = "string", - }; - - byte[] destination = new byte[1024]; - var serializer = FlatBufferSerializer.Default.Compile(); - - int bytesWritten = serializer.Write(default(SpanWriter), destination, t); - - byte[] stringBytes = Encoding.UTF8.GetBytes("string"); - - // We can't predict ordering since there is hashing under the hood. - int firstIndex = destination.AsSpan().IndexOf(stringBytes); - int secondIndex = destination.AsSpan().Slice(0, firstIndex + 1).IndexOf(stringBytes); - Assert.Equal(-1, secondIndex); - } - - [FlatBufferTable] - public class SharedStringsVector : object - { - [FlatBufferItem(0, SharedString = true)] - public virtual IList? StringVector { get; set; } - } - - [FlatBufferTable] - public class RegularStringsVector : object - { - [FlatBufferItem(0)] - public virtual IList? StringVector { get; set; } - } - - [FlatBufferTable] - public class SharedStringsTable : object - { - [FlatBufferItem(0, SharedString = true)] - public virtual string? String1 { get; set; } - - [FlatBufferItem(1, SharedString = true)] - public virtual string? String2 { get; set; } - - [FlatBufferItem(2, SharedString = true)] - public virtual string? String3 { get; set; } - } - - [FlatBufferTable] - public class RegularStringsTable : object - { - [FlatBufferItem(0)] - public virtual string? String1 { get; set; } - - [FlatBufferItem(1)] - public virtual string? String2 { get; set; } - - [FlatBufferItem(2)] - public virtual string? String3 { get; set; } - } -} diff --git a/src/Tests/FlatSharpTests/SerializationTests/TableSerializationTests.cs b/src/Tests/FlatSharpTests/SerializationTests/TableSerializationTests.cs index 4873e02a..2d56c666 100644 --- a/src/Tests/FlatSharpTests/SerializationTests/TableSerializationTests.cs +++ b/src/Tests/FlatSharpTests/SerializationTests/TableSerializationTests.cs @@ -25,112 +25,12 @@ namespace FlatSharpTests; public class TableSerializationTests { - [Fact] - public void AllMembersNull() - { - SimpleTable table = new SimpleTable(); - - byte[] buffer = new byte[1024]; - - byte[] expectedData = - { - 4, 0, 0, 0, - 252, 255, 255, 255, - 4, 0, - 4, 0, - }; - - int bytesWritten = FlatBufferSerializer.Default.Serialize(table, buffer); - Assert.True(expectedData.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, bytesWritten))); - } - [Fact] public void RootTableNull() { Assert.Throws(() => FlatBufferSerializer.Default.Serialize(null, new byte[1024])); } - [Fact] - public void TableWithStruct() - { - SimpleTable table = new SimpleTable - { - Struct = new SimpleStruct { Byte = 1, Long = 2, Uint = 3 } - }; - - byte[] buffer = new byte[1024]; - - byte[] expectedData = - { - 4, 0, 0, 0, // uoffset to table start - 236, 255, 255, 255, // soffet to vtable (4 - x = 24 => x = -20) - 2, 0, 0, 0, 0, 0, 0, 0, // struct.long - 1, // struct.byte - 0, 0, 0, // padding - 3, 0, 0, 0, // struct.uint - 8, 0, // vtable length - 20, 0, // table length - 0, 0, // index 0 offset - 4, 0, // Index 1 offset - }; - - int bytesWritten = FlatBufferSerializer.Default.Serialize(table, buffer); - Assert.True(expectedData.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, bytesWritten))); - } - - [Fact] - public void TableWithStructAndString() - { - SimpleTable table = new SimpleTable - { - String = "hi", - Struct = new SimpleStruct { Byte = 1, Long = 2, Uint = 3 } - }; - - byte[] buffer = new byte[1024]; - - byte[] expectedData = - { - 4, 0, 0, 0, // uoffset to table start - 232, 255, 255, 255, // soffet to vtable (4 - x = 24 => x = -20) - 2, 0, 0, 0, 0, 0, 0, 0, // struct.long - 1, 0, 0, 0, // struct.byte + padding - 3, 0, 0, 0, // struct.uint - 12, 0, 0, 0, // uoffset to string - 8, 0, // vtable length - 24, 0, // table length - 20, 0, // index 0 offset - 4, 0, // Index 1 offset - 2, 0, 0, 0, // string length - 104, 105, 0, // hi + null terminator - }; - - int bytesWritten = FlatBufferSerializer.Default.Serialize(table, buffer); - Assert.True(expectedData.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, bytesWritten))); - } - - [Fact] - public void EmptyTableSerialize() - { - EmptyTable table = new EmptyTable(); - - byte[] buffer = new byte[1024]; - - byte[] expectedData = - { - 4, 0, 0, 0, - 252, 255, 255, 255, - 4, 0, - 4, 0, - }; - - int bytesWritten = FlatBufferSerializer.Default.Serialize(table, buffer); - Assert.True(expectedData.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, bytesWritten))); - - int maxSize = FlatBufferSerializer.Default.GetMaxSize(table); - Assert.Equal(23, maxSize); - } - [Fact] public void TableParse_NotMutable() { diff --git a/src/Tests/FlatSharpTests/SerializationTests/VectorSerializationTests.cs b/src/Tests/FlatSharpTests/SerializationTests/VectorSerializationTests.cs index 3ba014d9..e0329d19 100644 --- a/src/Tests/FlatSharpTests/SerializationTests/VectorSerializationTests.cs +++ b/src/Tests/FlatSharpTests/SerializationTests/VectorSerializationTests.cs @@ -29,61 +29,6 @@ public class VectorSerializationTests { public class SimpleTests { - [Fact] - public void EmptyString() - { - var root = new RootTable - { - Vector = string.Empty, - }; - - Span target = new byte[10240]; - int offset = FlatBufferSerializer.Default.Serialize(root, target); - target = target.Slice(0, offset); - - byte[] expectedResult = - { - 4, 0, 0, 0, // offset to table start - 248, 255, 255, 255, // soffset to vtable (-8) - 12, 0, 0, 0, // uoffset_t to string - 6, 0, // vtable length - 8, 0, // table length - 4, 0, // offset of index 0 field - 0, 0, // padding to 4-byte alignment - 0, 0, 0, 0, // vector length - 0, // null terminator (special case for strings). - }; - - Assert.True(expectedResult.AsSpan().SequenceEqual(target)); - } - - [Fact] - public void SimpleString() - { - var root = new RootTable - { - Vector = new string(new char[] { (char)1, (char)2, (char)3 }), - }; - - Span target = new byte[10240]; - int offset = FlatBufferSerializer.Default.Serialize(root, target); - target = target.Slice(0, offset); - - byte[] expectedResult = - { - 4, 0, 0, 0, // offset to table start - 248, 255, 255, 255, // soffset to vtable (-8) - 12, 0, 0, 0, // uoffset_t to vector - 6, 0, // vtable length - 8, 0, // table length - 4, 0, // offset of index 0 field - 0, 0, // padding to 4-byte alignment - 3, 0, 0, 0, // vector length - 1, 2, 3, 0, // data + null terminator (special case for string vectors). - }; - - Assert.True(expectedResult.AsSpan().SequenceEqual(target)); - } [Fact] public void Simple_Scalar_Vectors() @@ -536,142 +481,6 @@ public void SortedVector_BinarySearch_ErrorCases() } } - public class IndexedVectorTests - { - [Fact] - public void IndexedVector_Simple() - { - var table = new RootTableSorted>> - { - Vector = new IndexedVector> - { - { new TableWithKey { Key = "a", Value = "AAA" } }, - { new TableWithKey { Key = "b", Value = "BBB" } }, - { new TableWithKey { Key = "c", Value = "CCC" } }, - } - }; - - var serializer = FlatBufferSerializer.Default.Compile>>>() - .WithSettings(s => s.UseLazyDeserialization()); - - byte[] data = new byte[1024 * 1024]; - serializer.Write(data, table); - - var parsed = serializer.Parse(data); - - Assert.Equal("AAA", parsed.Vector["a"].Value); - Assert.Equal("BBB", parsed.Vector["b"].Value); - Assert.Equal("CCC", parsed.Vector["c"].Value); - - Assert.True(parsed.Vector.TryGetValue("a", out var value) && value.Value == "AAA"); - Assert.True(parsed.Vector.TryGetValue("b", out value) && value.Value == "BBB"); - Assert.True(parsed.Vector.TryGetValue("c", out value) && value.Value == "CCC"); - } - - [Fact] - public void IndexedVector_RandomString() - { - var table = new RootTable>> - { - Vector = new IndexedVector>() - }; - - List keys = new List(); - for (int i = 0; i < 1000; ++i) - { - string key = Guid.NewGuid().ToString(); - keys.Add(key); - - table.Vector.AddOrReplace(new TableWithKey { Key = key, Value = Guid.NewGuid().ToString() }); - } - - byte[] data = new byte[10 * 1024 * 1024]; - var serializer = FlatBufferSerializer.Default.Compile>>>().WithSettings(s => s.UseLazyDeserialization()); - int bytesWritten = serializer.Write(data, table); - - var parsed = serializer.Parse(data); - - foreach (var key in keys) - { - Assert.Equal(table.Vector[key].Value, parsed.Vector[key].Value); - } - } - - [Fact] - public void IndexedVector_RandomByte() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomSByte() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomUShort() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomShort() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomUInt() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomInt() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomULong() => IndexedVectorScalarTest(); - - [Fact] - public void IndexedVector_RandomLong() => IndexedVectorScalarTest(); - - private void IndexedVectorScalarTest() where T : struct - { - foreach (FlatBufferDeserializationOption option in Enum.GetValues(typeof(FlatBufferDeserializationOption))) - { - var table = new RootTable>> - { - Vector = new IndexedVector>() - }; - - Random r = new Random(); - byte[] keyBuffer = new byte[8]; - List keys = new List(); - for (int i = 0; i < 1000; ++i) - { - r.NextBytes(keyBuffer); - T key = MemoryMarshal.Cast(keyBuffer)[0]; - keys.Add(key); - table.Vector.AddOrReplace(new TableWithKey { Key = key, Value = Guid.NewGuid().ToString() }); - } - - byte[] data = new byte[1024 * 1024]; - var serializer = FlatBufferSerializer.Default.Compile>>>().WithSettings(s => s.UseDeserializationMode(option)); - int bytesWritten = serializer.Write(data, table); - - var parsed = serializer.Parse>>>(data); - - foreach (var key in keys) - { - Assert.Equal(table.Vector[key].Value, parsed.Vector[key].Value); - } - - // verify sorted and that we can read it when it's from a normal vector. - var listSerializer = FlatBufferSerializer.Default.Compile>>>().WithSettings(s => s.UseDeserializationMode(option)); - var parsedList = listSerializer.Parse(data); - Assert.Equal(parsed.Vector.Count, parsedList.Vector.Count); - var previous = parsedList.Vector[0]; - for (int i = 1; i < parsedList.Vector.Count; ++i) - { - var item = parsedList.Vector[i]; - Assert.True(Comparer.Default.Compare(previous.Key, item.Key) <= 0); - - Assert.True(parsed.Vector.TryGetValue(item.Key, out var fromDict)); - Assert.Equal(item.Key, fromDict.Key); - Assert.Equal(item.Value, fromDict.Value); - - previous = item; - } - } - } - } - public class VectorOfUnionTests { [Fact]