Exemple #1
0
    public void NonGenericSerializer_FromInstance()
    {
        var flatBufferSerializer = new FlatBufferSerializer(FlatBufferDeserializationOption.Lazy);

        ISerializer serializer = flatBufferSerializer.Compile(new SomeTable());

        Assert.IsAssignableFrom <ISerializer <SomeTable> >(serializer);
        Assert.Equal(typeof(SomeTable), serializer.RootType);

        Assert.True(serializer.GetMaxSize(new SomeTable()) > 0);
        Assert.Throws <ArgumentNullException>(() => serializer.GetMaxSize(null));
        Assert.Throws <ArgumentException>(() => serializer.GetMaxSize(new SomeOtherTable()));

        var bw = new ArrayBufferWriter <byte>();

        Assert.Throws <ArgumentNullException>(() => serializer.Write(bw, null));
        Assert.Throws <ArgumentException>(() => serializer.Write(bw, new SomeOtherTable()));
        int written = serializer.Write(bw, new SomeTable {
            A = 3
        });

        Assert.True(written > 0);
        Assert.Equal(written, bw.WrittenCount);

        object parsed = serializer.Parse(bw.WrittenMemory);

        Assert.True(typeof(SomeTable).IsAssignableFrom(parsed.GetType()));
        Assert.NotEqual(typeof(SomeTable), parsed.GetType());
        Assert.IsAssignableFrom <IFlatBufferDeserializedObject>(parsed);

        var deserialized = (IFlatBufferDeserializedObject)parsed;

        Assert.Equal(typeof(SomeTable), deserialized.TableOrStructType);
        Assert.NotNull(deserialized.InputBuffer);     // lazy
        Assert.Equal(FlatBufferDeserializationOption.Lazy, deserialized.DeserializationContext.DeserializationOption);
        Assert.Equal(FlatBufferDeserializationOption.Lazy, serializer.DeserializationOption);

        ISerializer parsedSerializer  = flatBufferSerializer.Compile(deserialized);
        ISerializer parsedSerializer2 = flatBufferSerializer.Compile(parsed);

        Assert.Same(serializer, parsedSerializer);
        Assert.Same(serializer, parsedSerializer2);

        // Test that items deserialized from serializer 1 can be used by serializer 2. Why? Who knows!
        var         flatBufferSerializer2 = new FlatBufferSerializer(FlatBufferDeserializationOption.Lazy);
        ISerializer serializer3           = flatBufferSerializer2.Compile(parsed);

        Assert.Equal(typeof(SomeTable), serializer3.RootType);
        Assert.NotSame(serializer, serializer3);
        bw = new ArrayBufferWriter <byte>();
        serializer3.Write(bw, parsed);
    }
        public void Success_Nullable(FlatBufferDeserializationOption option)
        {
            FlatBufferSerializer serializer = new FlatBufferSerializer(option);

            WriteThroughTable_Required <ValueStruct?> table = new()
            {
                Item = new ValueStruct {
                    Value = 1
                }
            };

            byte[] result = new byte[1024];

            var code = serializer.Compile <WriteThroughTable_Required <ValueStruct?> >().CSharp;

            serializer.Serialize(table, result);

            var parsed = serializer.Parse <WriteThroughTable_Required <ValueStruct?> >(result);

            Assert.Equal(1, parsed.Item.Value.Value);

            parsed.Item = new ValueStruct {
                Value = 4
            };

            // Re-read and verify the in-struct writethrough succeeded.
            parsed = serializer.Parse <WriteThroughTable_Required <ValueStruct?> >(result);
            Assert.Equal(4, parsed.Item.Value.Value);

            var ex = Assert.Throws <InvalidOperationException>(() => parsed.Item = null);

            Assert.Equal(
                "Nullable object must have a value.",
                ex.Message);
        }
        public void Parse_NonNullable(Type type, bool enableMarshal, bool expectMarshal)
        {
            byte[] data =
            {
                4,     0,   0,   0,
                232, 255, 255, 255,
                1,     0,
                2,     0,
                3,     0,   0,   0,
                4,     0,   0,   0,0, 0, 0, 0,
                5,     0,   0,   0,
                6,     0,
                24,    0,
                4,     0,
            };

            Type tableType = typeof(SimpleTableNonNullable <>).MakeGenericType(type);
            var  fbs       = new FlatBufferSerializer(new FlatBufferSerializerOptions {
                EnableValueStructMemoryMarshalDeserialization = enableMarshal
            });

            ISerializer  serializer = fbs.Compile(tableType);
            ISimpleTable table      = (ISimpleTable)serializer.Parse(data);

            Assert.Equal(
                expectMarshal,
                serializer.CSharp.Contains($"MemoryMarshal.Cast<byte, {type.GetGlobalCompilableTypeName()}>"));

            Assert.Equal(1, table.Item.IA);
            Assert.Equal(2, table.Item.IB);
            Assert.Equal(3, table.Item.IC);
            Assert.Equal(4, table.Item.ID);
            Assert.Equal(5, table.Item.IInner.Test);
        }
        public void Serialize_SixteenByte(bool marshal)
        {
            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 <SixteenByteStruct?> source = new()
            {
                Item = new SixteenByteStruct
                {
                    A = 1,
                    B = 2ul,
                }
            };

            byte[] buffer = new byte[1024];

            var serializer = new FlatBufferSerializer(new FlatBufferSerializerOptions {
                EnableValueStructMemoryMarshalDeserialization = marshal
            });
            var written = serializer.Serialize(source, buffer);

            Assert.True(data.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, written)));
            Assert.Equal(marshal, serializer.Compile(source.GetType()).CSharp.Contains("MemoryMarshal.Cast"));
        }
    }
        public void Serialize_NineByte(bool marshal)
        {
            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 <NineByteStruct>
            {
                Item = new NineByteStruct
                {
                    A = 1,
                    B = 2,
                }
            };

            byte[] buffer = new byte[1024];

            var serializer = new FlatBufferSerializer(new FlatBufferSerializerOptions {
                EnableValueStructMemoryMarshalDeserialization = marshal
            });
            int bytesWritten = serializer.Serialize(table, buffer);

            Assert.True(data.AsSpan().SequenceEqual(buffer.AsSpan().Slice(0, bytesWritten)));
            Assert.Equal(marshal, serializer.Compile(table.GetType()).CSharp.Contains("MemoryMarshal.Cast"));
        }
    public void GreedyMemoryCopySerialization_NoEffect()
    {
        TestTable <Struct> t = new()
        {
            Foo    = "foobar",
            Struct = new Struct
            {
                Bar = 12,
            }
        };

        FlatBufferSerializer serializer = new FlatBufferSerializer(FlatBufferDeserializationOption.GreedyMutable);
        var compiled = serializer.Compile <TestTable <Struct> >().WithSettings(new SerializerSettings {
            EnableMemoryCopySerialization = true
        });

        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));
    }
Exemple #7
0
    public void Facade_NullReturnedFromConverter()
    {
        TypeModelContainer container = TypeModelContainer.CreateDefault();

        container.RegisterTypeFacade <string, DateTimeOffset, InvalidDateTimeStringConverter>();

        FlatBufferSerializer serializer = new FlatBufferSerializer(
            new FlatBufferSerializerOptions(FlatBufferDeserializationOption.Greedy),
            container);

        byte[] destination = new byte[200];
        var    compiled    = serializer.Compile <ExtensionTable <DateTimeOffset> >();

        try
        {
            serializer.Serialize(
                new ExtensionTable <DateTimeOffset> {
                Item = DateTimeOffset.UtcNow
            },
                destination);

            Assert.False(true, "expected exception");
        }
        catch (InvalidOperationException ex)
        {
            Assert.Contains("ITypeFacadeConverter", ex.Message);
        }
    }
Exemple #8
0
        private SimpleTable SerializeAndParse(FlatBufferSerializerOptions options, out WeakReference <byte[]> buffer)
        {
            SimpleTable table = new SimpleTable
            {
                String = "hi",
                Struct = new SimpleStruct {
                    Byte = 1, Long = 2, Uint = 3
                },
                StructVector = new List <SimpleStruct> {
                    new SimpleStruct {
                        Byte = 4, Long = 5, Uint = 6
                    }
                },
            };

            var serializer = new FlatBufferSerializer(options);

            var rawBuffer = new byte[1024];

            serializer.Serialize(table, rawBuffer);
            buffer = new WeakReference <byte[]>(rawBuffer);

            string csharp = serializer.Compile <SimpleTable>().CSharp;

            return(serializer.Parse <SimpleTable>(rawBuffer));
        }
        public void Success_NonNullable(FlatBufferDeserializationOption option)
        {
            FlatBufferSerializer serializer = new FlatBufferSerializer(option);

            WriteThroughTable_Required <ValueStruct> table = new()
            {
                Item = new ValueStruct {
                    Value = 1
                }
            };

            byte[] result = new byte[1024];

            var code = serializer.Compile <WriteThroughTable_Required <ValueStruct> >().CSharp;

            serializer.Serialize(table, result);

            var parsed = serializer.Parse <WriteThroughTable_Required <ValueStruct> >(result);

            Assert.Equal(1, parsed.Item.Value);

            parsed.Item = new ValueStruct {
                Value = 4
            };

            // Re-read and verify the in-struct writethrough succeeded.
            parsed = serializer.Parse <WriteThroughTable_Required <ValueStruct> >(result);
            Assert.Equal(4, parsed.Item.Value);
        }
Exemple #10
0
        public void Facade_NullReturnedFromConverter2()
        {
            TypeModelContainer container = TypeModelContainer.CreateDefault();

            container.RegisterTypeFacade <long?, DateTimeOffset?, InvalidNullableDateTimeNullableLongConverter>();

            FlatBufferSerializer serializer = new FlatBufferSerializer(
                new FlatBufferSerializerOptions(FlatBufferDeserializationOption.Greedy),
                container);

            byte[] destination = new byte[200];
            var    compiled    = serializer.Compile <ExtensionTable <DateTimeOffset?> >();

            try
            {
                serializer.Serialize(
                    new ExtensionTable <DateTimeOffset?> {
                    Item = DateTimeOffset.UtcNow
                },
                    destination);

                Assert.Fail();
            }
            catch (InvalidOperationException ex)
            {
                Assert.IsTrue(ex.Message.Contains("ITypeFacadeConverter"));
            }
        }
        public void Parse_SixteenByte(bool marshal)
        {
            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,
            };

            var serializer = new FlatBufferSerializer(new FlatBufferSerializerOptions {
                EnableValueStructMemoryMarshalDeserialization = marshal
            });
            var parsed = serializer.Parse <SimpleTableAnything <SixteenByteStruct?> >(data);

            Assert.NotNull(parsed);
            Assert.NotNull(parsed.Item);
            Assert.Equal(1, parsed.Item.Value.A);
            Assert.Equal(2ul, parsed.Item.Value.B);

            Assert.Equal(marshal, serializer.Compile <SimpleTableAnything <SixteenByteStruct?> >().CSharp.Contains("MemoryMarshal.Cast"));
        }
        public void NonGenericSerializer_FromInstance()
        {
            var flatBufferSerializer = new FlatBufferSerializer(FlatBufferDeserializationOption.Lazy);

            ISerializer serializer = flatBufferSerializer.Compile(new SomeTable());

            Assert.IsInstanceOfType(serializer, typeof(ISerializer <SomeTable>));
            Assert.AreEqual(typeof(SomeTable), serializer.RootType);

            Assert.IsTrue(serializer.GetMaxSize(new SomeTable()) > 0);
            Assert.ThrowsException <ArgumentNullException>(() => serializer.GetMaxSize(null));
            Assert.ThrowsException <ArgumentException>(() => serializer.GetMaxSize(new SomeOtherTable()));

            Memory <byte> data = new byte[1024];

            Assert.IsTrue(serializer.Write(data, new SomeTable {
                A = 3
            }) > 0);
            Assert.ThrowsException <ArgumentNullException>(() => serializer.Write(data, null));
            Assert.ThrowsException <ArgumentException>(() => serializer.Write(data, new SomeOtherTable()));

            object parsed = serializer.Parse(data);

            Assert.IsTrue(typeof(SomeTable).IsAssignableFrom(parsed.GetType()));
            Assert.AreNotEqual(typeof(SomeTable), parsed.GetType());
            Assert.IsInstanceOfType(parsed, typeof(IFlatBufferDeserializedObject));

            var deserialized = (IFlatBufferDeserializedObject)parsed;

            Assert.AreEqual(deserialized.TableOrStructType, typeof(SomeTable));
            Assert.IsNotNull(deserialized.InputBuffer); // lazy
            Assert.AreEqual(FlatBufferDeserializationOption.Lazy, deserialized.DeserializationContext.DeserializationOption);

            ISerializer parsedSerializer  = flatBufferSerializer.Compile(deserialized);
            ISerializer parsedSerializer2 = flatBufferSerializer.Compile(parsed);

            Assert.AreSame(serializer, parsedSerializer);
            Assert.AreSame(serializer, parsedSerializer2);

            // Test that items deserialized from serializer 1 can be used by serializer 2. Why? Who knows!
            var         flatBufferSerializer2 = new FlatBufferSerializer(FlatBufferDeserializationOption.Lazy);
            ISerializer serializer3           = flatBufferSerializer2.Compile(parsed);

            Assert.AreEqual(typeof(SomeTable), serializer3.RootType);
            Assert.AreNotSame(serializer, serializer3);
            serializer3.Write(data, parsed);
        }
        public void Failure_Greedy(FlatBufferDeserializationOption option)
        {
            FlatBufferSerializer serializer = new FlatBufferSerializer(option);
            var ex = Assert.Throws <InvalidFlatBufferDefinitionException>(() => serializer.Compile <WriteThroughTable_Required <ValueStruct> >());

            Assert.Equal(
                "Property 'Item' of Table 'FlatSharpTests.WriteThroughTests.WriteThroughTable_Required<FlatSharpTests.WriteThroughTests.ValueStruct>' specifies the WriteThrough option. However, WriteThrough is only supported when using deserialization option 'Progressive' or 'Lazy'.",
                ex.Message);
        }
        public void Failures(FlatBufferDeserializationOption option, Type vectorType)
        {
            FlatBufferSerializer serializer = new FlatBufferSerializer(option);
            var tableType = typeof(WriteThroughTable_NotRequired <>).MakeGenericType(vectorType);

            var ex = Assert.Throws <InvalidFlatBufferDefinitionException>(() => serializer.Compile(tableType));

            Assert.Equal(
                $"Field '{tableType.GetCompilableTypeName()}.Item' declares the WriteThrough option. WriteThrough is not supported when using Greedy deserialization.",
                ex.Message);
        }
        public void Serialize_NonNullable(Type type, bool enableMarshal, bool expectMarshal)
        {
            Type tableType = typeof(SimpleTableNonNullable <>).MakeGenericType(type);

            ISimpleTable simpleTable = (ISimpleTable)Activator.CreateInstance(tableType);

            IValidStruct s = (IValidStruct)Activator.CreateInstance(type);

            s.IA     = 1;
            s.IB     = 2;
            s.IC     = 3;
            s.ID     = 4;
            s.IInner = new ValidStruct_Inner {
                Test = 5
            };

            simpleTable.Item = s;

            var fbs = new FlatBufferSerializer(new FlatBufferSerializerOptions {
                EnableValueStructMemoryMarshalDeserialization = enableMarshal
            });

            ISerializer serializer = fbs.Compile(simpleTable);

            byte[] data         = new byte[1024];
            int    bytesWritten = serializer.Write(data, simpleTable);

            byte[] expectedData =
            {
                4,     0,   0,   0,
                232, 255, 255, 255,
                1,     0,
                2,     0,
                3,     0,   0,   0,
                4,     0,   0,   0,0, 0, 0, 0,
                5,     0,   0,   0,
                6,     0,
                24,    0,
                4,     0,
            };

            Assert.Equal(expectedData.Length, bytesWritten);
            Assert.True(expectedData.AsSpan().SequenceEqual(data.AsSpan().Slice(0, bytesWritten)));

            Assert.Equal(
                expectMarshal,
                serializer.CSharp.Contains($"MemoryMarshal.Cast<byte, {type.GetGlobalCompilableTypeName()}>"));
        }
    public void LazyMemoryCopySerialization()
    {
        TestTable <WriteThroughStruct> t = new()
        {
            Foo    = "foobar",
            Struct = new WriteThroughStruct
            {
                Bar = 12,
            }
        };

        FlatBufferSerializer serializer = new FlatBufferSerializer(FlatBufferDeserializationOption.Lazy);
        var compiled = serializer.Compile <TestTable <WriteThroughStruct> >().WithSettings(new SerializerSettings {
            EnableMemoryCopySerialization = true
        });

        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 <BufferTooSmallException>(() => 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));
    }
Exemple #17
0
    private void FacadeTest <TUnderlyingType, TType, TConverter>(
        TUnderlyingType underlyingValue,
        TType value,
        TypeModelContainer container = null) where TConverter : struct, ITypeFacadeConverter <TUnderlyingType, TType>
    {
        if (container == null)
        {
            container = TypeModelContainer.CreateDefault();
            container.RegisterTypeFacade <TUnderlyingType, TType, TConverter>();
        }

        FlatBufferSerializer serializer = new FlatBufferSerializer(
            new FlatBufferSerializerOptions(FlatBufferDeserializationOption.Greedy),
            container);

        byte[] destination  = new byte[1024];
        byte[] destination2 = new byte[1024];

        var compiled = serializer.Compile <ExtensionTable <TType> >();

        var underlyingItem = new ExtensionTable <TUnderlyingType> {
            Item = underlyingValue
        };
        var facadeItem = new ExtensionTable <TType> {
            Item = value
        };

        serializer.Serialize(facadeItem, destination);
        serializer.Serialize(underlyingItem, destination2);

        Assert.True(destination.AsSpan().SequenceEqual(destination2));

        var parsed = serializer.Parse <ExtensionTable <TType> >(destination);

        Assert.Equal(parsed.Item, value);
        Assert.Equal(serializer.GetMaxSize(facadeItem), serializer.GetMaxSize(underlyingItem));
    }
    public void WriteThrough_InvalidDeserializationOption()
    {
        foreach (FlatBufferDeserializationOption option in Enum.GetValues(typeof(FlatBufferDeserializationOption)))
        {
            if (option == FlatBufferDeserializationOption.Progressive || option == FlatBufferDeserializationOption.Lazy)
            {
                continue;
            }

            var serializer = new FlatBufferSerializer(option);
            var ex         = Assert.Throws <InvalidFlatBufferDefinitionException>(() => serializer.Compile <Table <WriteThroughStruct <bool> > >());

            Assert.Equal(
                "Property 'Value' of Struct 'FlatSharpTests.WriteThroughTests.WriteThroughStruct<System.Boolean>' specifies the WriteThrough option. However, WriteThrough is only supported when using deserialization option 'Progressive' or 'Lazy'.",
                ex.Message);
        }
    }
Exemple #19
0
        private void RunTest <T>(string fbsType, int length, FlatBufferDeserializationOption option) where T : struct
        {
            string schema = $@"
            namespace StructVectorTests;

            table Table ({MetadataKeys.SerializerKind}) {{
                foo:Foo;
            }}
            struct Foo {{
              V:[{fbsType}:{length}];
            }}";

            Assembly asm = FlatSharpCompiler.CompileAndLoadAssembly(
                schema,
                new());

            Type tableType = asm.GetType("StructVectorTests.Table");
            Type fooType   = asm.GetType("StructVectorTests.Foo");
            var  typeModel = TypeModelContainer.CreateDefault().CreateTypeModel(fooType);

            Assert.AreEqual(FlatBufferSchemaType.Struct, typeModel.SchemaType);

            for (int i = 0; i < length; ++i)
            {
                PropertyInfo p = fooType.GetProperty($"__flatsharp__V_{i}");
                Assert.IsTrue(p.GetMethod.IsPublic);
                Assert.IsTrue(p.SetMethod.IsPublic);
                Assert.IsTrue(p.GetMethod.IsVirtual);
                Assert.IsTrue(p.SetMethod.IsVirtual);

                var attr = p.GetCustomAttribute <FlatBufferItemAttribute>();
                Assert.IsNotNull(attr);
                Assert.AreEqual(i, attr.Index);

                var browsableAttr = p.GetCustomAttribute <EditorBrowsableAttribute>();
                Assert.IsNotNull(browsableAttr);
                Assert.AreEqual(EditorBrowsableState.Advanced, browsableAttr.State);
            }

            var vectorProperty = fooType.GetProperty("V");

            Assert.IsNull(vectorProperty.GetCustomAttribute <FlatBufferItemAttribute>()); // pseudo-item, not actual.
            Assert.IsNull(vectorProperty.GetCustomAttribute <EditorBrowsableAttribute>());
            Assert.IsTrue(vectorProperty.GetMethod.IsPublic);
            Assert.IsFalse(vectorProperty.GetMethod.IsVirtual);
            Assert.IsNull(vectorProperty.SetMethod);

            dynamic table = Activator.CreateInstance(tableType);
            dynamic foo   = Activator.CreateInstance(fooType);

            table.foo = foo;

            Assert.AreEqual(length, foo.V.Count);

            for (int i = 0; i < length; ++i)
            {
                foo.V[i] = GetRandom <T>();
            }

            byte[] data = new byte[1024];

            var fbs = new FlatBufferSerializer(
                new FlatBufferSerializerOptions(option)
            {
                EnableAppDomainInterceptOnAssemblyLoad = true
            });

            var serializer = fbs.Compile((object)table);

            serializer.Write(data, (object)table);
            dynamic parsed = serializer.Parse(data);

            dynamic copy = Activator.CreateInstance(tableType, (object)parsed);

            Assert.AreEqual(length, parsed.foo.V.Count);
            for (int i = 0; i < length; ++i)
            {
                CheckRandom <T>(foo.V[i], parsed.foo.V[i]);
                CheckRandom <T>(foo.V[i], copy.foo.V[i]);
            }

            bool isMutable = option is FlatBufferDeserializationOption.VectorCacheMutable or FlatBufferDeserializationOption.GreedyMutable;

            if (length == 0)
            {
                return;
            }

            try
            {
                for (int i = 0; i < length; ++i)
                {
                    parsed.foo.V[i] = GetRandom <T>();
                }

                Assert.IsTrue(isMutable);
            }
            catch (NotMutableException)
            {
                Assert.IsFalse(isMutable);
            }
        }
Exemple #20
0
    private void RunTest <T>(string fbsType, int length, FlatBufferDeserializationOption option) where T : struct
    {
        string schema = $@"
            {MetadataHelpers.AllAttributes}
            namespace StructVectorTests;

            table Table ({MetadataKeys.SerializerKind}) {{
                foo:Foo;
            }}
            struct Foo {{
              V:[{fbsType}:{length}];
            }}";

        (Assembly asm, string code) = FlatSharpCompiler.CompileAndLoadAssemblyWithCode(
            schema,
            new());

        Type tableType = asm.GetType("StructVectorTests.Table");
        Type fooType   = asm.GetType("StructVectorTests.Foo");
        var  typeModel = TypeModelContainer.CreateDefault().CreateTypeModel(fooType);

        Assert.Equal(FlatBufferSchemaType.Struct, typeModel.SchemaType);

        for (int i = 0; i < length; ++i)
        {
            PropertyInfo p = fooType.GetProperty($"__flatsharp__V_{i}", BindingFlags.Instance | BindingFlags.NonPublic);
            Assert.True(p.GetMethod.IsFamily);
            Assert.True(p.SetMethod.IsFamily);
            Assert.True(p.GetMethod.IsVirtual);
            Assert.True(p.SetMethod.IsVirtual);

            var metaAttr = p.GetCustomAttribute <FlatBufferMetadataAttribute>();
            var attr     = p.GetCustomAttribute <FlatBufferItemAttribute>();
            Assert.NotNull(attr);
            Assert.NotNull(metaAttr);
            Assert.Equal(i, attr.Index);
            Assert.Equal(FlatBufferMetadataKind.Accessor, metaAttr.Kind);
            Assert.Equal($"V[{attr.Index}]", metaAttr.Value);
        }

        var vectorProperty = fooType.GetProperty("V");

        Assert.Null(vectorProperty.GetCustomAttribute <FlatBufferItemAttribute>()); // pseudo-item, not actual.
        Assert.True(vectorProperty.GetMethod.IsPublic);
        Assert.False(vectorProperty.GetMethod.IsVirtual);
        Assert.Null(vectorProperty.SetMethod);

        dynamic table = Activator.CreateInstance(tableType);
        dynamic foo   = Activator.CreateInstance(fooType);

        table.foo = foo;

        Assert.Equal(length, foo.V.Count);

        // Test copyFrom with full array.
        List <T> items = new List <T>();

        for (int i = 0; i < length; ++i)
        {
            items.Add(GetRandom <T>());
        }

        table.foo.V.CopyFrom((IReadOnlyList <T>)items);
        for (int i = 0; i < length; ++i)
        {
            CheckRandom <T>(items[i], table.foo.V[i]);
        }

        for (int i = 0; i < length; ++i)
        {
            foo.V[i] = GetRandom <T>();
        }

        byte[] data = new byte[1024];

        var fbs = new FlatBufferSerializer(
            new FlatBufferSerializerOptions(option)
        {
            EnableAppDomainInterceptOnAssemblyLoad = true
        });

        var serializer = fbs.Compile((object)table);

        serializer.Write(data, (object)table);
        dynamic parsed = serializer.Parse(data);

        dynamic copy = Activator.CreateInstance(tableType, (object)parsed);

        Assert.Equal(length, parsed.foo.V.Count);
        for (int i = 0; i < length; ++i)
        {
            CheckRandom <T>(foo.V[i], parsed.foo.V[i]);
            CheckRandom <T>(foo.V[i], copy.foo.V[i]);
        }

        bool isMutable = option is FlatBufferDeserializationOption.GreedyMutable;

        if (length == 0)
        {
            return;
        }

        try
        {
            for (int i = 0; i < length; ++i)
            {
                parsed.foo.V[i] = GetRandom <T>();
            }

            Assert.True(isMutable);
        }
        catch (NotMutableException)
        {
            Assert.False(isMutable);
        }
    }