From 9939087259ae85c8170ed3a5cd2dbd8dd963a76d Mon Sep 17 00:00:00 2001 From: TJKoury Date: Mon, 27 Oct 2025 20:35:31 -0700 Subject: [PATCH] add --preserve-case --- .gitignore | 3 +- BUILD.bazel | 3 + CMakeLists.txt | 12 +- dart/test_preserve_case/.gitignore | 1 + dart/test_preserve_case/bool_structs.fbs | 10 + dart/test_preserve_case/enums.fbs | 10 + .../test_preserve_case/flat_buffers_test.dart | 1026 +++++ .../test_preserve_case/flex_builder_test.dart | 647 ++++ dart/test_preserve_case/flex_reader_test.dart | 1032 +++++ dart/test_preserve_case/flex_types_test.dart | 278 ++ dart/test_preserve_case/monster_test.fbs | 180 + dart/test_preserve_case/monsterdata_test.mon | Bin 0 -> 600 bytes include/flatbuffers/flatc.h | 1 + include/flatbuffers/idlnames.h | 41 + include/flatbuffers/options.h | 12 + src/BUILD.bazel | 1 + src/flatc.cpp | 36 + src/flatc_main.cpp | 8 +- src/idl_gen_dart.cpp | 147 +- src/idl_gen_go.cpp | 258 +- src/idl_gen_python.cpp | 31 +- src/idl_parser.cpp | 20 +- src/options.cpp | 6 + src/util.cpp | 9 + tests/.gitignore | 3 +- tests/CppPreserveCaseTest.cpp | 95 + tests/CppTest.sh | 56 + tests/DartTest.sh | 72 +- tests/GoTest.sh | 115 +- tests/JavaTest.sh | 95 + tests/JsonSchemaTest.sh | 60 + tests/PHPTest.sh | 49 + tests/PythonTest.sh | 58 +- tests/RustTest.sh | 74 +- tests/TestAll.sh | 4 +- tests/TypeScriptTest.sh | 23 + tests/__init__.py | 0 tests/go_test_preserve_case.go | 2575 +++++++++++++ .../JavaPreserveCaseTest.java | 96 + tests/phpTest.php | 33 +- tests/phpTestPreserveCase.php | 651 ++++ tests/phpUnionVectorTest.php | 81 +- tests/phpUnionVectorTest.sh | 13 +- tests/phpUnionVectorTestPreserveCase.php | 118 + tests/py_test_preserve_case.py | 3412 +++++++++++++++++ .../tests/arrays_test_preserve_case.rs | 342 ++ .../tests/integration_test_preserve_case.rs | 3235 ++++++++++++++++ .../tests/more_defaults_test_preserve_case.rs | 35 + .../optional_scalars_test_preserve_case.rs | 124 + tests/service_test_generated.py | 84 +- tests/service_test_grpc_fb.py | 97 + tests/service_test_grpc_fb.pyi | 27 + ...JavaScriptComplexArraysPreserveCaseTest.js | 149 + tests/ts/JavaScriptPreserveCaseTest.js | 548 +++ tests/ts/JavaScriptTestv1PreserveCase.cjs | 409 ++ ...riptUnionUnderlyingTypePreserveCaseTest.js | 39 + .../JavaScriptUnionVectorPreserveCaseTest.js | 105 + tests/ts/TypeScriptTest.py | 171 + tests/ts/preserve_case_aliases.js | 60 + tests/ts/tsconfig.node.preserve_case.json | 4 + tests/ts/tsconfig.preserve_case.json | 5 + 61 files changed, 16648 insertions(+), 241 deletions(-) create mode 100644 dart/test_preserve_case/.gitignore create mode 100644 dart/test_preserve_case/bool_structs.fbs create mode 100644 dart/test_preserve_case/enums.fbs create mode 100644 dart/test_preserve_case/flat_buffers_test.dart create mode 100644 dart/test_preserve_case/flex_builder_test.dart create mode 100644 dart/test_preserve_case/flex_reader_test.dart create mode 100644 dart/test_preserve_case/flex_types_test.dart create mode 100644 dart/test_preserve_case/monster_test.fbs create mode 100644 dart/test_preserve_case/monsterdata_test.mon create mode 100644 include/flatbuffers/idlnames.h create mode 100644 include/flatbuffers/options.h create mode 100644 src/options.cpp create mode 100644 tests/CppPreserveCaseTest.cpp create mode 100755 tests/CppTest.sh create mode 100755 tests/JavaTest.sh create mode 100755 tests/JsonSchemaTest.sh create mode 100755 tests/PHPTest.sh create mode 100755 tests/TypeScriptTest.sh create mode 100644 tests/__init__.py create mode 100644 tests/go_test_preserve_case.go create mode 100644 tests/java_preserve_case/JavaPreserveCaseTest.java create mode 100644 tests/phpTestPreserveCase.php create mode 100644 tests/phpUnionVectorTestPreserveCase.php create mode 100644 tests/py_test_preserve_case.py create mode 100644 tests/rust_usage_test/tests/arrays_test_preserve_case.rs create mode 100644 tests/rust_usage_test/tests/integration_test_preserve_case.rs create mode 100644 tests/rust_usage_test/tests/more_defaults_test_preserve_case.rs create mode 100644 tests/rust_usage_test/tests/optional_scalars_test_preserve_case.rs create mode 100644 tests/service_test_grpc_fb.py create mode 100644 tests/service_test_grpc_fb.pyi create mode 100644 tests/ts/JavaScriptComplexArraysPreserveCaseTest.js create mode 100644 tests/ts/JavaScriptPreserveCaseTest.js create mode 100644 tests/ts/JavaScriptTestv1PreserveCase.cjs create mode 100644 tests/ts/JavaScriptUnionUnderlyingTypePreserveCaseTest.js create mode 100644 tests/ts/JavaScriptUnionVectorPreserveCaseTest.js create mode 100644 tests/ts/preserve_case_aliases.js create mode 100644 tests/ts/tsconfig.node.preserve_case.json create mode 100644 tests/ts/tsconfig.preserve_case.json diff --git a/.gitignore b/.gitignore index 0296f8fac36..6a9028b8e81 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,8 @@ tests/monsterdata_javascript_wire.mon tests/monsterdata_lobster_wire.mon tests/monsterdata_rust_wire.mon tests/php/ +tests/ts/preserve_case/ +tests/java_preserve_case_*/ CMakeLists.txt.user CMakeScripts/** CTestTestfile.cmake @@ -156,4 +158,3 @@ kotlin/**/generated MODULE.bazel.lock # Ignore the generated docs -docs/site \ No newline at end of file diff --git a/BUILD.bazel b/BUILD.bazel index 20a4fd60a4d..da40f1914d5 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -71,13 +71,16 @@ filegroup( "include/flatbuffers/detached_buffer.h", "include/flatbuffers/file_manager.h", "include/flatbuffers/flatbuffer_builder.h", + "include/flatbuffers/flatc.h", "include/flatbuffers/flatbuffers.h", "include/flatbuffers/flex_flat_util.h", "include/flatbuffers/flexbuffers.h", "include/flatbuffers/grpc.h", "include/flatbuffers/hash.h", "include/flatbuffers/idl.h", + "include/flatbuffers/idlnames.h", "include/flatbuffers/minireflect.h", + "include/flatbuffers/options.h", "include/flatbuffers/reflection.h", "include/flatbuffers/reflection_generated.h", "include/flatbuffers/registry.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index f7f388f83ec..fdfa877d8b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,6 @@ endif() add_definitions(-DFLATBUFFERS_LOCALE_INDEPENDENT=$) if(NOT WIN32) - include(CheckSymbolExists) check_symbol_exists(realpath "stdlib.h" HAVE_REALPATH) if(NOT HAVE_REALPATH) add_definitions(-DFLATBUFFERS_NO_ABSOLUTE_PATH_RESOLUTION) @@ -136,6 +135,7 @@ set(FlatBuffers_Library_SRCS include/flatbuffers/hash.h include/flatbuffers/idl.h include/flatbuffers/minireflect.h + include/flatbuffers/options.h include/flatbuffers/reflection.h include/flatbuffers/reflection_generated.h include/flatbuffers/registry.h @@ -151,6 +151,7 @@ set(FlatBuffers_Library_SRCS src/idl_gen_text.cpp src/reflection.cpp src/util.cpp + src/options.cpp ) set(FlatBuffers_Compiler_SRCS @@ -449,26 +450,35 @@ if(FLATBUFFERS_BUILD_FLATLIB) endif() endif() +option(ENABLE_DEBUG_SYMBOLS "Enable debug symbols for flatc" OFF) + if(FLATBUFFERS_BUILD_FLATC) add_executable(flatc ${FlatBuffers_Compiler_SRCS}) + if(FLATBUFFERS_ENABLE_PCH) add_pch_to_target(flatc include/flatbuffers/pch/flatc_pch.h) endif() target_link_libraries(flatc PRIVATE $) + target_compile_options(flatc PRIVATE $<$,$>: /MT > + $<$: + -g -O0 + > ) if(FLATBUFFERS_CODE_SANITIZE AND NOT WIN32) add_fsanitize_to_target(flatc ${FLATBUFFERS_CODE_SANITIZE}) endif() + if(NOT FLATBUFFERS_FLATC_EXECUTABLE) set(FLATBUFFERS_FLATC_EXECUTABLE $) endif() + if(FLATBUFFERS_STATIC_FLATC AND NOT MSVC) target_link_libraries(flatc PRIVATE -static) endif() diff --git a/dart/test_preserve_case/.gitignore b/dart/test_preserve_case/.gitignore new file mode 100644 index 00000000000..8ee65775f48 --- /dev/null +++ b/dart/test_preserve_case/.gitignore @@ -0,0 +1 @@ +*_generated.dart diff --git a/dart/test_preserve_case/bool_structs.fbs b/dart/test_preserve_case/bool_structs.fbs new file mode 100644 index 00000000000..47b26b5b1bd --- /dev/null +++ b/dart/test_preserve_case/bool_structs.fbs @@ -0,0 +1,10 @@ +// Test for #7355 +table Foo { + my_foo : foo_properties; +} + +struct foo_properties +{ + a : bool; + b : bool; +} diff --git a/dart/test_preserve_case/enums.fbs b/dart/test_preserve_case/enums.fbs new file mode 100644 index 00000000000..a4272a43bc0 --- /dev/null +++ b/dart/test_preserve_case/enums.fbs @@ -0,0 +1,10 @@ +enum OptionsEnum : uint32 +{ + A = 1, + B = 2, + C = 3 +} + +table MyTable { + options : [OptionsEnum]; +} diff --git a/dart/test_preserve_case/flat_buffers_test.dart b/dart/test_preserve_case/flat_buffers_test.dart new file mode 100644 index 00000000000..275a2ef4092 --- /dev/null +++ b/dart/test_preserve_case/flat_buffers_test.dart @@ -0,0 +1,1026 @@ +import 'dart:io' as io; +import 'dart:typed_data'; + +import 'package:flat_buffers/flat_buffers.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import './bool_structs_generated.dart' as example4; +import './monster_test_my_game.example2_generated.dart' as example2; +import './monster_test_my_game.example_generated.dart' as example; +import 'enums_generated.dart' as example3; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(BuilderTest); + defineReflectiveTests(ObjectAPITest); + defineReflectiveTests(CheckOtherLangaugesData); + defineReflectiveTests(GeneratorTest); + defineReflectiveTests(ListOfEnumsTest); + }); +} + +int indexToField(int index) { + return (1 + 1 + index) * 2; +} + +@reflectiveTest +class CheckOtherLangaugesData { + test_cppData() async { + List data = await io.File( + path.join(path.context.current, 'test', 'monsterdata_test.mon'), + ).readAsBytes(); + example.Monster mon = example.Monster(data); + expect(mon.hp, 80); + expect(mon.mana, 150); + expect(mon.name, 'MyMonster'); + expect(mon.pos!.x, 1.0); + expect(mon.pos!.y, 2.0); + expect(mon.pos!.z, 3.0); + expect(mon.pos!.test1, 3.0); + expect(mon.pos!.test2.value, 2.0); + expect(mon.pos!.test3.a, 5); + expect(mon.pos!.test3.b, 6); + expect(mon.testType!.value, example.AnyTypeId.Monster.value); + expect(mon.test is example.Monster, true); + final monster2 = mon.test as example.Monster; + expect(monster2.name, "Fred"); + + expect(mon.inventory!.length, 5); + expect(mon.inventory!.reduce((cur, next) => cur + next), 10); + final test4 = mon.test4!; + expect(test4.length, 2); + expect(test4[0].a + test4[0].b + test4[1].a + test4[1].b, 100); + expect(mon.testarrayofstring!.length, 2); + expect(mon.testarrayofstring![0], "test1"); + expect(mon.testarrayofstring![1], "test2"); + + // this will fail if accessing any field fails. + expect( + mon.toString(), + 'Monster{' + 'pos: Vec3{x: 1.0, y: 2.0, z: 3.0, test1: 3.0, test2: Color.Green, test3: Test{a: 5, b: 6}}, ' + 'mana: 150, hp: 80, name: MyMonster, inventory: [0, 1, 2, 3, 4], ' + 'color: Color.Blue, testType: AnyTypeId.Monster, ' + 'test: Monster{pos: null, mana: 150, hp: 100, name: Fred, ' + 'inventory: null, color: Color.Blue, testType: null, ' + 'test: null, test4: null, testarrayofstring: null, ' + 'testarrayoftables: null, enemy: null, testnestedflatbuffer: null, ' + 'testempty: null, testbool: false, testhashs32Fnv1: 0, ' + 'testhashu32Fnv1: 0, testhashs64Fnv1: 0, testhashu64Fnv1: 0, ' + 'testhashs32Fnv1a: 0, testhashu32Fnv1a: 0, testhashs64Fnv1a: 0, ' + 'testhashu64Fnv1a: 0, testarrayofbools: null, testf: 3.14159, ' + 'testf2: 3.0, testf3: 0.0, testarrayofstring2: null, ' + 'testarrayofsortedstruct: null, flex: null, test5: null, ' + 'vectorOfLongs: null, vectorOfDoubles: null, parentNamespaceTest: null, ' + 'vectorOfReferrables: null, singleWeakReference: 0, ' + 'vectorOfWeakReferences: null, vectorOfStrongReferrables: null, ' + 'coOwningReference: 0, vectorOfCoOwningReferences: null, ' + 'nonOwningReference: 0, vectorOfNonOwningReferences: null, ' + 'anyUniqueType: null, anyUnique: null, anyAmbiguousType: null, ' + 'anyAmbiguous: null, vectorOfEnums: null, signedEnum: Race.None, ' + 'testrequirednestedflatbuffer: null, scalarKeySortedTables: null, ' + 'nativeInline: null, ' + 'longEnumNonEnumDefault: LongEnum._default, ' + 'longEnumNormalDefault: LongEnum.LongOne, nanDefault: NaN, ' + 'infDefault: Infinity, positiveInfDefault: Infinity, infinityDefault: ' + 'Infinity, positiveInfinityDefault: Infinity, negativeInfDefault: ' + '-Infinity, negativeInfinityDefault: -Infinity, doubleInfDefault: Infinity}, ' + 'test4: [Test{a: 10, b: 20}, Test{a: 30, b: 40}], ' + 'testarrayofstring: [test1, test2], testarrayoftables: null, ' + 'enemy: Monster{pos: null, mana: 150, hp: 100, name: Fred, ' + 'inventory: null, color: Color.Blue, testType: null, ' + 'test: null, test4: null, testarrayofstring: null, ' + 'testarrayoftables: null, enemy: null, testnestedflatbuffer: null, ' + 'testempty: null, testbool: false, testhashs32Fnv1: 0, ' + 'testhashu32Fnv1: 0, testhashs64Fnv1: 0, testhashu64Fnv1: 0, ' + 'testhashs32Fnv1a: 0, testhashu32Fnv1a: 0, testhashs64Fnv1a: 0, ' + 'testhashu64Fnv1a: 0, testarrayofbools: null, testf: 3.14159, ' + 'testf2: 3.0, testf3: 0.0, testarrayofstring2: null, ' + 'testarrayofsortedstruct: null, flex: null, test5: null, ' + 'vectorOfLongs: null, vectorOfDoubles: null, parentNamespaceTest: null, ' + 'vectorOfReferrables: null, singleWeakReference: 0, ' + 'vectorOfWeakReferences: null, vectorOfStrongReferrables: null, ' + 'coOwningReference: 0, vectorOfCoOwningReferences: null, ' + 'nonOwningReference: 0, vectorOfNonOwningReferences: null, ' + 'anyUniqueType: null, anyUnique: null, anyAmbiguousType: null, ' + 'anyAmbiguous: null, vectorOfEnums: null, signedEnum: Race.None, ' + 'testrequirednestedflatbuffer: null, scalarKeySortedTables: null, ' + 'nativeInline: null, ' + 'longEnumNonEnumDefault: LongEnum._default, ' + 'longEnumNormalDefault: LongEnum.LongOne, nanDefault: NaN, ' + 'infDefault: Infinity, positiveInfDefault: Infinity, infinityDefault: ' + 'Infinity, positiveInfinityDefault: Infinity, negativeInfDefault: ' + '-Infinity, negativeInfinityDefault: -Infinity, doubleInfDefault: Infinity}, ' + 'testnestedflatbuffer: null, testempty: null, testbool: true, ' + 'testhashs32Fnv1: -579221183, testhashu32Fnv1: 3715746113, ' + 'testhashs64Fnv1: 7930699090847568257, ' + 'testhashu64Fnv1: 7930699090847568257, ' + 'testhashs32Fnv1a: -1904106383, testhashu32Fnv1a: 2390860913, ' + 'testhashs64Fnv1a: 4898026182817603057, ' + 'testhashu64Fnv1a: 4898026182817603057, ' + 'testarrayofbools: [true, false, true], testf: 3.14159, testf2: 3.0, ' + 'testf3: 0.0, testarrayofstring2: null, testarrayofsortedstruct: [' + 'Ability{id: 0, distance: 45}, Ability{id: 1, distance: 21}, ' + 'Ability{id: 5, distance: 12}], ' + 'flex: null, test5: [Test{a: 10, b: 20}, Test{a: 30, b: 40}], ' + 'vectorOfLongs: [1, 100, 10000, 1000000, 100000000], ' + 'vectorOfDoubles: [-1.7976931348623157e+308, 0.0, 1.7976931348623157e+308], ' + 'parentNamespaceTest: null, vectorOfReferrables: null, ' + 'singleWeakReference: 0, vectorOfWeakReferences: null, ' + 'vectorOfStrongReferrables: null, coOwningReference: 0, ' + 'vectorOfCoOwningReferences: null, nonOwningReference: 0, ' + 'vectorOfNonOwningReferences: null, ' + 'anyUniqueType: null, anyUnique: null, ' + 'anyAmbiguousType: null, ' + 'anyAmbiguous: null, vectorOfEnums: null, signedEnum: Race.None, ' + 'testrequirednestedflatbuffer: null, scalarKeySortedTables: [Stat{id: ' + 'miss, val: 0, count: 0}, Stat{id: hit, val: 10, count: 1}], ' + 'nativeInline: Test{a: 1, b: 2}, ' + 'longEnumNonEnumDefault: LongEnum._default, ' + 'longEnumNormalDefault: LongEnum.LongOne, nanDefault: NaN, ' + 'infDefault: Infinity, positiveInfDefault: Infinity, infinityDefault: ' + 'Infinity, positiveInfinityDefault: Infinity, negativeInfDefault: ' + '-Infinity, negativeInfinityDefault: -Infinity, doubleInfDefault: Infinity}', + ); + } +} + +/// Test a custom, fixed-memory allocator (no actual allocations performed) +class CustomAllocator extends Allocator { + final _memory = ByteData(10 * 1024); + int _used = 0; + + Uint8List buffer(int size) => _memory.buffer.asUint8List(_used - size, size); + + @override + ByteData allocate(int size) { + if (size > _memory.lengthInBytes) { + throw UnsupportedError('Trying to allocate too much'); + } + _used = size; + return ByteData.sublistView(_memory, 0, size); + } + + @override + void deallocate(ByteData _) {} +} + +@reflectiveTest +class BuilderTest { + void test_monsterBuilder([Builder? builder]) { + final fbBuilder = builder ?? Builder(); + final str = fbBuilder.writeString('MyMonster'); + + fbBuilder.writeString('test1'); + fbBuilder.writeString('test2', asciiOptimization: true); + final testArrayOfString = fbBuilder.endStructVector(2); + + final fred = fbBuilder.writeString('Fred'); + + final List treasure = [0, 1, 2, 3, 4]; + final inventory = fbBuilder.writeListUint8(treasure); + + final monBuilder = example.MonsterBuilder(fbBuilder) + ..begin() + ..addNameOffset(fred); + final mon2 = monBuilder.finish(); + + final testBuilder = example.TestBuilder(fbBuilder); + testBuilder.finish(10, 20); + testBuilder.finish(30, 40); + final test4 = fbBuilder.endStructVector(2); + + monBuilder + ..begin() + ..addPos( + example.Vec3Builder(fbBuilder).finish( + 1.0, + 2.0, + 3.0, + 3.0, + example.Color.Green, + () => testBuilder.finish(5, 6), + ), + ) + ..addHp(80) + ..addNameOffset(str) + ..addInventoryOffset(inventory) + ..addTestType(example.AnyTypeId.Monster) + ..addTestOffset(mon2) + ..addTest4Offset(test4) + ..addTestarrayofstringOffset(testArrayOfString); + final mon = monBuilder.finish(); + fbBuilder.finish(mon); + + final mon3 = example.Monster(fbBuilder.buffer); + expect(mon3.name, 'MyMonster'); + expect(mon3.pos!.test1, 3.0); + } + + void test_error_addInt32_withoutStartTable([Builder? builder]) { + builder ??= Builder(); + expect(() { + builder!.addInt32(0, 0); + }, throwsA(isA())); + } + + void test_error_addOffset_withoutStartTable() { + Builder builder = Builder(); + expect(() { + builder.addOffset(0, 0); + }, throwsA(isA())); + } + + void test_error_endTable_withoutStartTable() { + Builder builder = Builder(); + expect(() { + builder.endTable(); + }, throwsA(isA())); + } + + void test_error_startTable_duringTable() { + Builder builder = Builder(); + builder.startTable(0); + expect(() { + builder.startTable(0); + }, throwsA(isA())); + } + + void test_error_writeString_duringTable() { + Builder builder = Builder(); + builder.startTable(1); + expect(() { + builder.writeString('12345'); + }, throwsA(isA())); + } + + void test_file_identifier() { + Uint8List byteList; + { + Builder builder = Builder(initialSize: 0); + builder.startTable(0); + int offset = builder.endTable(); + builder.finish(offset, 'Az~ÿ'); + byteList = builder.buffer; + } + // Convert byteList to a ByteData so that we can read data from it. + ByteData byteData = byteList.buffer.asByteData(byteList.offsetInBytes); + // First 4 bytes are an offset to the table data. + int tableDataLoc = byteData.getUint32(0, Endian.little); + // Next 4 bytes are the file identifier. + expect(byteData.getUint8(4), 65); // 'a' + expect(byteData.getUint8(5), 122); // 'z' + expect(byteData.getUint8(6), 126); // '~' + expect(byteData.getUint8(7), 255); // 'ÿ' + // First 4 bytes of the table data are a backwards offset to the vtable. + int vTableLoc = + tableDataLoc - byteData.getInt32(tableDataLoc, Endian.little); + // First 2 bytes of the vtable are the size of the vtable in bytes, which + // should be 4. + expect(byteData.getUint16(vTableLoc, Endian.little), 4); + // Next 2 bytes are the size of the object in bytes (including the vtable + // pointer), which should be 4. + expect(byteData.getUint16(vTableLoc + 2, Endian.little), 4); + } + + void test_low() { + final allocator = CustomAllocator(); + final builder = Builder(initialSize: 0, allocator: allocator); + + builder.putUint8(1); + expect(allocator.buffer(builder.size()), [1]); + + builder.putUint32(2); + expect(allocator.buffer(builder.size()), [2, 0, 0, 0, 0, 0, 0, 1]); + + builder.putUint8(3); + expect(allocator.buffer(builder.size()), [ + 0, + 0, + 0, + 3, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ]); + + builder.putUint8(4); + expect(allocator.buffer(builder.size()), [ + 0, + 0, + 4, + 3, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ]); + + builder.putUint8(5); + expect(allocator.buffer(builder.size()), [ + 0, + 5, + 4, + 3, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ]); + + builder.putUint32(6); + expect(allocator.buffer(builder.size()), [ + 6, + 0, + 0, + 0, + 0, + 5, + 4, + 3, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ]); + } + + void test_table_default() { + List byteList; + { + final builder = Builder(initialSize: 0, allocator: CustomAllocator()); + builder.startTable(2); + builder.addInt32(0, 10, 10); + builder.addInt32(1, 20, 10); + int offset = builder.endTable(); + builder.finish(offset); + byteList = builder.buffer; + expect(builder.size(), byteList.length); + } + // read and verify + BufferContext buffer = BufferContext.fromBytes(byteList); + int objectOffset = buffer.derefObject(0); + // was not written, so uses the new default value + expect( + const Int32Reader().vTableGet(buffer, objectOffset, indexToField(0), 15), + 15, + ); + // has the written value + expect( + const Int32Reader().vTableGet(buffer, objectOffset, indexToField(1), 15), + 20, + ); + } + + void test_table_format([Builder? builder]) { + Uint8List byteList; + { + builder ??= Builder(initialSize: 0); + builder.startTable(3); + builder.addInt32(0, 10); + builder.addInt32(1, 20); + builder.addInt32(2, 30); + builder.finish(builder.endTable()); + byteList = builder.buffer; + } + // Convert byteList to a ByteData so that we can read data from it. + ByteData byteData = byteList.buffer.asByteData(byteList.offsetInBytes); + // First 4 bytes are an offset to the table data. + int tableDataLoc = byteData.getUint32(0, Endian.little); + // First 4 bytes of the table data are a backwards offset to the vtable. + int vTableLoc = + tableDataLoc - byteData.getInt32(tableDataLoc, Endian.little); + // First 2 bytes of the vtable are the size of the vtable in bytes, which + // should be 10. + expect(byteData.getUint16(vTableLoc, Endian.little), 10); + // Next 2 bytes are the size of the object in bytes (including the vtable + // pointer), which should be 16. + expect(byteData.getUint16(vTableLoc + 2, Endian.little), 16); + // Remaining 6 bytes are the offsets within the object where the ints are + // located. + for (int i = 0; i < 3; i++) { + int offset = byteData.getUint16(vTableLoc + 4 + 2 * i, Endian.little); + expect( + byteData.getInt32(tableDataLoc + offset, Endian.little), + 10 + 10 * i, + ); + } + } + + void test_table_string() { + String latinString = 'test'; + String unicodeString = 'Проба пера'; + List byteList; + { + Builder builder = Builder(initialSize: 0); + int? latinStringOffset = builder.writeString( + latinString, + asciiOptimization: true, + ); + int? unicodeStringOffset = builder.writeString( + unicodeString, + asciiOptimization: true, + ); + builder.startTable(2); + builder.addOffset(0, latinStringOffset); + builder.addOffset(1, unicodeStringOffset); + int offset = builder.endTable(); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + int objectOffset = buf.derefObject(0); + expect( + const StringReader().vTableGetNullable( + buf, + objectOffset, + indexToField(0), + ), + latinString, + ); + expect( + const StringReader( + asciiOptimization: true, + ).vTableGetNullable(buf, objectOffset, indexToField(1)), + unicodeString, + ); + } + + void test_table_types([Builder? builder]) { + List byteList; + { + builder ??= Builder(initialSize: 0); + int? stringOffset = builder.writeString('12345'); + builder.startTable(7); + builder.addBool(0, true); + builder.addInt8(1, 10); + builder.addInt32(2, 20); + builder.addOffset(3, stringOffset); + builder.addInt32(4, 40); + builder.addUint32(5, 0x9ABCDEF0); + builder.addUint8(6, 0x9A); + int offset = builder.endTable(); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + int objectOffset = buf.derefObject(0); + expect( + const BoolReader().vTableGetNullable(buf, objectOffset, indexToField(0)), + true, + ); + expect( + const Int8Reader().vTableGetNullable(buf, objectOffset, indexToField(1)), + 10, + ); + expect( + const Int32Reader().vTableGetNullable(buf, objectOffset, indexToField(2)), + 20, + ); + expect( + const StringReader().vTableGetNullable( + buf, + objectOffset, + indexToField(3), + ), + '12345', + ); + expect( + const Int32Reader().vTableGetNullable(buf, objectOffset, indexToField(4)), + 40, + ); + expect( + const Uint32Reader().vTableGetNullable( + buf, + objectOffset, + indexToField(5), + ), + 0x9ABCDEF0, + ); + expect( + const Uint8Reader().vTableGetNullable(buf, objectOffset, indexToField(6)), + 0x9A, + ); + } + + void test_writeList_of_Uint32() { + List values = [10, 100, 12345, 0x9abcdef0]; + // write + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListUint32(values); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const Uint32ListReader().read(buf, 0); + expect(items, hasLength(4)); + expect(items, orderedEquals(values)); + } + + void test_writeList_ofBool() { + void verifyListBooleans(int len, List trueBits) { + // write + List byteList; + { + Builder builder = Builder(initialSize: 0); + List values = List.filled(len, false); + for (int bit in trueBits) { + values[bit] = true; + } + int offset = builder.writeListBool(values); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const BoolListReader().read(buf, 0); + expect(items, hasLength(len)); + for (int i = 0; i < items.length; i++) { + expect(items[i], trueBits.contains(i), reason: 'bit $i of $len'); + } + } + + verifyListBooleans(0, []); + verifyListBooleans(1, []); + verifyListBooleans(1, [0]); + verifyListBooleans(31, [0, 1]); + verifyListBooleans(31, [1, 2, 24, 25, 30]); + verifyListBooleans(31, [0, 30]); + verifyListBooleans(32, [1, 2, 24, 25, 31]); + verifyListBooleans(33, [1, 2, 24, 25, 32]); + verifyListBooleans(33, [1, 2, 24, 25, 31, 32]); + verifyListBooleans(63, []); + verifyListBooleans(63, [0, 1, 2, 61, 62]); + verifyListBooleans(63, List.generate(63, (i) => i)); + verifyListBooleans(64, []); + verifyListBooleans(64, [0, 1, 2, 61, 62, 63]); + verifyListBooleans(64, [1, 2, 62]); + verifyListBooleans(64, [0, 1, 2, 63]); + verifyListBooleans(64, List.generate(64, (i) => i)); + verifyListBooleans(100, [0, 3, 30, 60, 90, 99]); + } + + void test_writeList_ofInt32() { + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListInt32([1, 2, 3, 4, 5]); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const ListReader(Int32Reader()).read(buf, 0); + expect(items, hasLength(5)); + expect(items, orderedEquals([1, 2, 3, 4, 5])); + } + + void test_writeList_ofFloat64() { + List values = [-1.234567, 3.4E+9, -5.6E-13, 7.8, 12.13]; + // write + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListFloat64(values); + builder.finish(offset); + byteList = builder.buffer; + } + + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const Float64ListReader().read(buf, 0); + + expect(items, hasLength(values.length)); + for (int i = 0; i < values.length; i++) { + expect(values[i], closeTo(items[i], .001)); + } + } + + void test_writeList_ofFloat32() { + List values = [1.0, 2.23, -3.213, 7.8, 12.13]; + // write + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListFloat32(values); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const Float32ListReader().read(buf, 0); + expect(items, hasLength(5)); + for (int i = 0; i < values.length; i++) { + expect(values[i], closeTo(items[i], .001)); + } + } + + void test_writeList_ofObjects([Builder? builder]) { + List byteList; + { + builder ??= Builder(initialSize: 0); + // write the object #1 + int object1; + { + builder.startTable(2); + builder.addInt32(0, 10); + builder.addInt32(1, 20); + object1 = builder.endTable(); + } + // write the object #1 + int object2; + { + builder.startTable(2); + builder.addInt32(0, 100); + builder.addInt32(1, 200); + object2 = builder.endTable(); + } + // write the list + int offset = builder.writeList([object1, object2]); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const ListReader( + TestPointReader(), + ).read(buf, 0); + expect(items, hasLength(2)); + expect(items[0].x, 10); + expect(items[0].y, 20); + expect(items[1].x, 100); + expect(items[1].y, 200); + } + + void test_writeList_ofStrings_asRoot() { + List byteList; + { + Builder builder = Builder(initialSize: 0); + int? str1 = builder.writeString('12345'); + int? str2 = builder.writeString('ABC'); + int offset = builder.writeList([str1, str2]); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const ListReader(StringReader()).read(buf, 0); + expect(items, hasLength(2)); + expect(items, contains('12345')); + expect(items, contains('ABC')); + } + + void test_writeList_ofStrings_inObject([Builder? builder]) { + List byteList; + { + builder ??= Builder(initialSize: 0); + int listOffset = builder.writeList([ + builder.writeString('12345'), + builder.writeString('ABC'), + ]); + builder.startTable(1); + builder.addOffset(0, listOffset); + int offset = builder.endTable(); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + StringListWrapperImpl reader = StringListWrapperReader().read(buf, 0); + List? items = reader.items; + expect(items, hasLength(2)); + expect(items, contains('12345')); + expect(items, contains('ABC')); + } + + void test_writeList_ofUint32() { + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListUint32([1, 2, 0x9ABCDEF0]); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const Uint32ListReader().read(buf, 0); + expect(items, hasLength(3)); + expect(items, orderedEquals([1, 2, 0x9ABCDEF0])); + } + + void test_writeList_ofUint16() { + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListUint16([1, 2, 60000]); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + List items = const Uint16ListReader().read(buf, 0); + expect(items, hasLength(3)); + expect(items, orderedEquals([1, 2, 60000])); + } + + void test_writeList_ofUint8() { + List byteList; + { + Builder builder = Builder(initialSize: 0); + int offset = builder.writeListUint8([1, 2, 3, 4, 0x9A, 0xFA]); + builder.finish(offset); + byteList = builder.buffer; + } + // read and verify + BufferContext buf = BufferContext.fromBytes(byteList); + const buffOffset = 8; // 32-bit offset to the list, + 32-bit length + for (final lazy in [true, false]) { + List items = Uint8ListReader(lazy: lazy).read(buf, 0); + expect(items, hasLength(6)); + expect(items, orderedEquals([1, 2, 3, 4, 0x9A, 0xFA])); + + // overwrite the buffer to verify the laziness + buf.buffer.setUint8(buffOffset + 1, 99); + expect(items, orderedEquals([1, lazy ? 99 : 2, 3, 4, 0x9A, 0xFA])); + + // restore the previous value for the next loop + buf.buffer.setUint8(buffOffset + 1, 2); + } + } + + void test_reset() { + // We'll run a selection of tests , reusing the builder between them. + final testCases = [ + test_monsterBuilder, + test_error_addInt32_withoutStartTable, + test_table_format, + test_table_types, + test_writeList_ofObjects, + test_writeList_ofStrings_inObject, + ]; + + // Execute all test cases in all permutations of their order. + // To do that, we generate permutations of test case indexes. + final testCasesPermutations = _permutationsOf( + List.generate(testCases.length, (index) => index), + ); + expect(testCasesPermutations.length, _factorial(testCases.length)); + + for (var indexes in testCasesPermutations) { + // print the order so failures are reproducible + printOnFailure('Running reset() test cases in order: $indexes'); + + Builder? builder; + for (var index in indexes) { + if (builder == null) { + // Initial size small enough so at least one test case increases it. + // On the other hand, it's large enough so that some test cases don't. + builder = Builder(initialSize: 32); + } else { + builder.reset(); + } + testCases[index](builder); + } + } + } + + // Generate permutations of the given list + List> _permutationsOf(List source) { + final result = >[]; + + void permutate(List items, int startAt) { + for (var i = startAt; i < items.length; i++) { + List permutation = items.toList(growable: false); + permutation[i] = items[startAt]; + permutation[startAt] = items[i]; + + // add the current list upon reaching the end + if (startAt == items.length - 1) { + result.add(items); + } else { + permutate(permutation, startAt + 1); + } + } + } + + permutate(source, 0); + return result; + } + + // a very simple implementation of n! + int _factorial(int n) { + var result = 1; + for (var i = 2; i <= n; i++) { + result *= i; + } + return result; + } +} + +@reflectiveTest +class ObjectAPITest { + void test_tableStat() { + final object1 = example.StatT(count: 3, id: "foo", val: 4); + final fbb = Builder(); + fbb.finish(object1.pack(fbb)); + final object2 = example.Stat(fbb.buffer).unpack(); + expect(object2.count, object1.count); + expect(object2.id, object1.id); + expect(object2.val, object1.val); + expect(object2.toString(), object1.toString()); + } + + void test_tableMonster() { + final monster = example.MonsterT() + ..pos = example.Vec3T( + x: 1, + y: 2, + z: 3, + test1: 4.0, + test2: example.Color.Red, + test3: example.TestT(a: 1, b: 2), + ) + ..mana = 2 + ..name = 'Monstrous' + ..inventory = [24, 42] + ..color = example.Color.Green + // TODO be smarter for unions and automatically set the `type` field? + ..testType = example.AnyTypeId.MyGame_Example2_Monster + ..test = example2.MonsterT() + ..test4 = [example.TestT(a: 3, b: 4), example.TestT(a: 5, b: 6)] + ..testarrayofstring = ["foo", "bar"] + ..testarrayoftables = [example.MonsterT(name: 'Oof')] + ..enemy = example.MonsterT(name: 'Enemy') + ..testarrayofbools = [false, true, false] + ..testf = 42.24 + ..testarrayofsortedstruct = [ + example.AbilityT(id: 1, distance: 5), + example.AbilityT(id: 3, distance: 7), + ] + ..vectorOfLongs = [5, 6, 7] + ..vectorOfDoubles = [8.9, 9.0, 10.1, 11.2] + ..anyAmbiguousType = example.AnyAmbiguousAliasesTypeId.M2 + ..anyAmbiguous = null + ..vectorOfEnums = [example.Color.Blue, example.Color.Green] + ..signedEnum = example.Race.None; + + final fbBuilder = Builder(); + final offset = monster.pack(fbBuilder); + expect(offset, isNonZero); + fbBuilder.finish(offset); + final data = fbBuilder.buffer; + + // TODO currently broken because of struct builder issue, see #6688 + // final monster2 = example.Monster(data); // Monster (reader) + // expect( + // // map Monster => MonsterT, Vec3 => Vec3T, ... + // monster2.toString().replaceAllMapped( + // RegExp('([a-zA-z0-9]+){'), (match) => match.group(1) + 'T{'), + // monster.toString()); + // + // final monster3 = monster2.unpack(); // MonsterT + // expect(monster3.toString(), monster.toString()); + } + + void test_Lists() { + // Ensure unpack() reads lists eagerly by reusing the same builder and + // overwriting data. Why: because standard reader reads lists lazily... + final fbb = Builder(); + + final object1 = example.TypeAliasesT(v8: [1, 2, 3], vf64: [5, 6]); + fbb.finish(object1.pack(fbb)); + final object1Read = example.TypeAliases(fbb.buffer).unpack(); + + // overwrite the original buffer by writing to the same builder + fbb.reset(); + final object2 = example.TypeAliasesT(v8: [7, 8, 9], vf64: [10, 11]); + fbb.finish(object2.pack(fbb)); + final object2Read = example.TypeAliases(fbb.buffer).unpack(); + + // this is fine even with lazy lists: + expect(object2.toString(), object2Read.toString()); + + // this fails with lazy lists: + expect(object1.toString(), object1Read.toString()); + + // empty list must be serialized as such (were stored NULL before v2.0) + fbb.reset(); + final object3 = example.TypeAliasesT(v8: [], vf64: null); + fbb.finish(object3.pack(fbb)); + final object3Read = example.TypeAliases(fbb.buffer).unpack(); + expect(object3.toString(), object3Read.toString()); + } +} + +class StringListWrapperImpl { + final BufferContext bp; + final int offset; + + StringListWrapperImpl(this.bp, this.offset); + + List? get items => const ListReader( + StringReader(), + ).vTableGetNullable(bp, offset, indexToField(0)); +} + +class StringListWrapperReader extends TableReader { + const StringListWrapperReader(); + + @override + StringListWrapperImpl createObject(BufferContext object, int offset) { + return StringListWrapperImpl(object, offset); + } +} + +class TestPointImpl { + final BufferContext bp; + final int offset; + + TestPointImpl(this.bp, this.offset); + + int get x => const Int32Reader().vTableGet(bp, offset, indexToField(0), 0); + + int get y => const Int32Reader().vTableGet(bp, offset, indexToField(1), 0); +} + +class TestPointReader extends TableReader { + const TestPointReader(); + + @override + TestPointImpl createObject(BufferContext object, int offset) { + return TestPointImpl(object, offset); + } +} + +@reflectiveTest +class GeneratorTest { + void test_constantEnumValues() async { + expect(example.Color.values, same(example.Color.values)); + expect(example.Race.values, same(example.Race.values)); + expect(example.AnyTypeId.values, same(example.AnyTypeId.values)); + expect( + example.AnyUniqueAliasesTypeId.values, + same(example.AnyUniqueAliasesTypeId.values), + ); + expect( + example.AnyAmbiguousAliasesTypeId.values, + same(example.AnyAmbiguousAliasesTypeId.values), + ); + } +} + +// See #6869 +@reflectiveTest +class ListOfEnumsTest { + void test_listOfEnums() async { + var mytable = example3.MyTableObjectBuilder( + options: [ + example3.OptionsEnum.A, + example3.OptionsEnum.B, + example3.OptionsEnum.C, + ], + ); + var bytes = mytable.toBytes(); + var mytable_read = example3.MyTable(bytes); + expect(mytable_read.options![0].value, example3.OptionsEnum.A.value); + expect(mytable_read.options![1].value, example3.OptionsEnum.B.value); + expect(mytable_read.options![2].value, example3.OptionsEnum.C.value); + } +} + +@reflectiveTest +class BoolInStructTest { + void test_boolInStruct() async { + var mystruct = example4.FooObjectBuilder( + myFoo: example4.FooPropertiesObjectBuilder(a: true, b: false), + ); + var bytes = mystruct.toBytes(); + var mystruct_read = example4.Foo(bytes); + expect(mystruct_read.myFoo!.a, true); + expect(mystruct_read.myFoo!.b, false); + } +} diff --git a/dart/test_preserve_case/flex_builder_test.dart b/dart/test_preserve_case/flex_builder_test.dart new file mode 100644 index 00000000000..0f766de7385 --- /dev/null +++ b/dart/test_preserve_case/flex_builder_test.dart @@ -0,0 +1,647 @@ +import 'dart:typed_data'; + +import 'package:flat_buffers/flex_buffers.dart' show Builder; +import 'package:test/test.dart'; + +void main() { + test('build with single value', () { + { + var flx = Builder(); + flx.addNull(); + expect(flx.finish(), [0, 0, 1]); + } + { + var flx = Builder(); + flx.addBool(true); + expect(flx.finish(), [1, 104, 1]); + } + { + var flx = Builder(); + flx.addBool(false); + expect(flx.finish(), [0, 104, 1]); + } + { + var flx = Builder(); + flx.addInt(1); + expect(flx.finish(), [1, 4, 1]); + } + { + var flx = Builder(); + flx.addInt(230); + expect(flx.finish(), [230, 0, 5, 2]); + } + { + var flx = Builder(); + flx.addInt(1025); + expect(flx.finish(), [1, 4, 5, 2]); + } + { + var flx = Builder(); + flx.addInt(-1025); + expect(flx.finish(), [255, 251, 5, 2]); + } + { + var builder = Builder()..addDouble(1.0); + expect(builder.finish(), [0, 0, 128, 63, 14, 4]); + } + { + var flx = Builder(); + flx.addDouble(0.1); + expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, 15, 8]); + } + { + var flx = Builder(); + flx.addDouble(0.5); + expect(flx.finish(), [0, 0, 0, 63, 14, 4]); + } + { + var flx = Builder(); + flx.addString('Maxim'); + expect(flx.finish(), [5, 77, 97, 120, 105, 109, 0, 6, 20, 1]); + } + { + var flx = Builder(); + flx.addString('hello 😱'); + expect(flx.finish(), [ + 10, + 104, + 101, + 108, + 108, + 111, + 32, + 240, + 159, + 152, + 177, + 0, + 11, + 20, + 1, + ]); + } + }); + + test('build vector', () { + { + var flx = Builder() + ..startVector() + ..addInt(1) + ..addInt(2) + ..end(); + expect(flx.finish(), [1, 2, 2, 64, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(-1) + ..addInt(256) + ..end(); + expect(flx.finish(), [255, 255, 0, 1, 4, 65, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(-45) + ..addInt(256000) + ..end(); + expect(flx.finish(), [211, 255, 255, 255, 0, 232, 3, 0, 8, 66, 1]); + } + { + var flx = Builder() + ..startVector() + ..addDouble(1.1) + ..addDouble(-256) + ..end(); + expect(flx.finish(), [ + 154, + 153, + 153, + 153, + 153, + 153, + 241, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 112, + 192, + 16, + 75, + 1, + ]); + } + { + var flx = Builder() + ..startVector() + ..addInt(1) + ..addInt(2) + ..addInt(4) + ..end(); + expect(flx.finish(), [1, 2, 4, 3, 76, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(-1) + ..addInt(256) + ..addInt(4) + ..end(); + expect(flx.finish(), [255, 255, 0, 1, 4, 0, 6, 77, 1]); + } + { + var flx = Builder() + ..startVector() + ..startVector() + ..addInt(61) + ..end() + ..addInt(64) + ..end(); + expect(flx.finish(), [1, 61, 2, 2, 64, 44, 4, 4, 40, 1]); + } + { + var flx = Builder() + ..startVector() + ..addString('foo') + ..addString('bar') + ..addString('baz') + ..end(); + expect(flx.finish(), [ + 3, + 102, + 111, + 111, + 0, + 3, + 98, + 97, + 114, + 0, + 3, + 98, + 97, + 122, + 0, + 3, + 15, + 11, + 7, + 3, + 60, + 1, + ]); + } + { + var flx = Builder() + ..startVector() + ..addString('foo') + ..addString('bar') + ..addString('baz') + ..addString('foo') + ..addString('bar') + ..addString('baz') + ..end(); + expect(flx.finish(), [ + 3, + 102, + 111, + 111, + 0, + 3, + 98, + 97, + 114, + 0, + 3, + 98, + 97, + 122, + 0, + 6, + 15, + 11, + 7, + 18, + 14, + 10, + 6, + 60, + 1, + ]); + } + { + var flx = Builder() + ..startVector() + ..addBool(true) + ..addBool(false) + ..addBool(true) + ..end(); + expect(flx.finish(), [3, 1, 0, 1, 3, 144, 1]); + } + { + var flx = Builder() + ..startVector() + ..addString('foo') + ..addInt(1) + ..addInt(-5) + ..addDouble(1.3) + ..addBool(true) + ..end(); + expect(flx.finish(), [ + 3, + 102, + 111, + 111, + 0, + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 251, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 205, + 204, + 204, + 204, + 204, + 204, + 244, + 63, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20, + 4, + 4, + 15, + 104, + 45, + 43, + 1, + ]); + } + }); + + test('build map', () { + { + var flx = Builder() + ..startMap() + ..addKey('a') + ..addInt(12) + ..end(); + expect(flx.finish(), [97, 0, 1, 3, 1, 1, 1, 12, 4, 2, 36, 1]); + } + { + var flx = Builder() + ..startMap() + ..addKey('a') + ..addInt(12) + ..addKey('') + ..addInt(45) + ..end(); + expect(flx.finish(), [ + 97, + 0, + 0, + 2, + 2, + 5, + 2, + 1, + 2, + 45, + 12, + 4, + 4, + 4, + 36, + 1, + ]); + } + { + var flx = Builder() + ..startVector() + ..startMap() + ..addKey('something') + ..addInt(12) + ..end() + ..startMap() + ..addKey('something') + ..addInt(45) + ..end() + ..end(); + expect(flx.finish(), [ + 115, + 111, + 109, + 101, + 116, + 104, + 105, + 110, + 103, + 0, + 1, + 11, + 1, + 1, + 1, + 12, + 4, + 6, + 1, + 1, + 45, + 4, + 2, + 8, + 4, + 36, + 36, + 4, + 40, + 1, + ]); + } + }); + + test('build blob', () { + { + var flx = Builder()..addBlob(Uint8List.fromList([1, 2, 3]).buffer); + expect(flx.finish(), [3, 1, 2, 3, 3, 100, 1]); + } + }); + + test('build from object', () { + expect( + Builder.buildFromObject( + Uint8List.fromList([1, 2, 3]).buffer, + ).asUint8List(), + [3, 1, 2, 3, 3, 100, 1], + ); + expect(Builder.buildFromObject(null).asUint8List(), [0, 0, 1]); + expect(Builder.buildFromObject(true).asUint8List(), [1, 104, 1]); + expect(Builder.buildFromObject(false).asUint8List(), [0, 104, 1]); + expect(Builder.buildFromObject(25).asUint8List(), [25, 4, 1]); + expect(Builder.buildFromObject(-250).asUint8List(), [6, 255, 5, 2]); + expect(Builder.buildFromObject(-2.50).asUint8List(), [ + 0, + 0, + 32, + 192, + 14, + 4, + ]); + expect(Builder.buildFromObject('Maxim').asUint8List(), [ + 5, + 77, + 97, + 120, + 105, + 109, + 0, + 6, + 20, + 1, + ]); + expect( + Builder.buildFromObject([1, 3.3, 'max', true, null, false]).asUint8List(), + [ + 3, + 109, + 97, + 120, + 0, + 0, + 0, + 0, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 102, + 102, + 102, + 102, + 102, + 102, + 10, + 64, + 31, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4, + 15, + 20, + 104, + 0, + 104, + 54, + 43, + 1, + ], + ); + expect( + Builder.buildFromObject([ + {'something': 12}, + {'something': 45}, + ]).asUint8List(), + [ + 115, + 111, + 109, + 101, + 116, + 104, + 105, + 110, + 103, + 0, + 1, + 11, + 1, + 1, + 1, + 12, + 4, + 6, + 1, + 1, + 45, + 4, + 2, + 8, + 4, + 36, + 36, + 4, + 40, + 1, + ], + ); + }); + + test('add double indirectly', () { + var flx = Builder()..addDoubleIndirectly(0.1); + expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, 8, 35, 1]); + }); + + test('add double indirectly to vector with cache', () { + var flx = Builder() + ..startVector() + ..addDoubleIndirectly(0.1, cache: true) + ..addDoubleIndirectly(0.1, cache: true) + ..addDoubleIndirectly(0.1, cache: true) + ..addDoubleIndirectly(0.1, cache: true) + ..end(); + expect(flx.finish(), [ + 154, + 153, + 153, + 153, + 153, + 153, + 185, + 63, + 4, + 9, + 10, + 11, + 12, + 35, + 35, + 35, + 35, + 8, + 40, + 1, + ]); + }); + + test('add int indirectly', () { + var flx = Builder()..addIntIndirectly(2345234523452345); + expect(flx.finish(), [185, 115, 175, 118, 250, 84, 8, 0, 8, 27, 1]); + }); + + test('add int indirectly to vector with cache', () { + var flx = Builder() + ..startVector() + ..addIntIndirectly(2345234523452345, cache: true) + ..addIntIndirectly(2345234523452345, cache: true) + ..addIntIndirectly(2345234523452345, cache: true) + ..addIntIndirectly(2345234523452345, cache: true) + ..end(); + expect(flx.finish(), [ + 185, + 115, + 175, + 118, + 250, + 84, + 8, + 0, + 4, + 9, + 10, + 11, + 12, + 27, + 27, + 27, + 27, + 8, + 40, + 1, + ]); + }); + + test('snapshot', () { + var flx = Builder(); + flx.startVector(); + flx.addInt(12); + expect(flx.snapshot().asUint8List(), [1, 12, 1, 44, 1]); + flx.addInt(24); + expect(flx.snapshot().asUint8List(), [12, 24, 2, 64, 1]); + flx.addInt(45); + expect(flx.snapshot().asUint8List(), [12, 24, 45, 3, 76, 1]); + }); +} diff --git a/dart/test_preserve_case/flex_reader_test.dart b/dart/test_preserve_case/flex_reader_test.dart new file mode 100644 index 00000000000..d77d1da462c --- /dev/null +++ b/dart/test_preserve_case/flex_reader_test.dart @@ -0,0 +1,1032 @@ +import 'dart:typed_data'; + +import 'package:flat_buffers/flex_buffers.dart' show Reference, Builder; +import 'package:test/test.dart'; + +void main() { + test('is null', () { + expect(Reference.fromBuffer(b([0, 0, 1])).isNull, isTrue); + }); + + test('bool value', () { + expect(Reference.fromBuffer(b([1, 104, 1])).boolValue, isTrue); + expect(Reference.fromBuffer(b([0, 104, 1])).boolValue, isFalse); + }); + test('int value', () { + expect(Reference.fromBuffer(b([25, 4, 1])).intValue, 25); + expect(Reference.fromBuffer(b([231, 4, 1])).intValue, -25); + expect(Reference.fromBuffer(b([230, 8, 1])).intValue, 230); + expect(Reference.fromBuffer(b([230, 0, 5, 2])).intValue, 230); + expect(Reference.fromBuffer(b([1, 4, 5, 2])).intValue, 1025); + expect(Reference.fromBuffer(b([255, 251, 5, 2])).intValue, -1025); + expect(Reference.fromBuffer(b([1, 4, 9, 2])).intValue, 1025); + expect( + Reference.fromBuffer(b([255, 255, 255, 127, 6, 4])).intValue, + 2147483647, + ); + expect(Reference.fromBuffer(b([0, 0, 0, 128, 6, 4])).intValue, -2147483648); + expect( + Reference.fromBuffer(b([255, 255, 255, 255, 0, 0, 0, 0, 7, 8])).intValue, + 4294967295, + ); + expect( + Reference.fromBuffer( + b([255, 255, 255, 255, 255, 255, 255, 127, 7, 8]), + ).intValue, + 9223372036854775807, + ); + expect( + Reference.fromBuffer(b([0, 0, 0, 0, 0, 0, 0, 128, 7, 8])).intValue, + -9223372036854775808, + ); + // Dart does not really support UInt64 + // expect(FlxValue.fromBuffer(b([255, 255, 255, 255, 255, 255, 255, 255, 11, 8])).intValue, 18446744073709551615); + }); + test('double value', () { + expect(Reference.fromBuffer(b([0, 0, 128, 63, 14, 4])).doubleValue, 1.0); + expect(Reference.fromBuffer(b([0, 0, 144, 64, 14, 4])).doubleValue, 4.5); + expect( + Reference.fromBuffer(b([205, 204, 204, 61, 14, 4])).doubleValue, + closeTo(.1, .001), + ); + expect( + Reference.fromBuffer( + b([154, 153, 153, 153, 153, 153, 185, 63, 15, 8]), + ).doubleValue, + .1, + ); + }); + test('num value', () { + expect(Reference.fromBuffer(b([0, 0, 144, 64, 14, 4])).numValue, 4.5); + expect( + Reference.fromBuffer(b([205, 204, 204, 61, 14, 4])).numValue, + closeTo(.1, .001), + ); + expect( + Reference.fromBuffer( + b([154, 153, 153, 153, 153, 153, 185, 63, 15, 8]), + ).numValue, + .1, + ); + expect(Reference.fromBuffer(b([255, 251, 5, 2])).numValue, -1025); + }); + test('string value', () { + expect( + Reference.fromBuffer( + b([5, 77, 97, 120, 105, 109, 0, 6, 20, 1]), + ).stringValue, + 'Maxim', + ); + expect( + Reference.fromBuffer( + b([10, 104, 101, 108, 108, 111, 32, 240, 159, 152, 177, 0, 11, 20, 1]), + ).stringValue, + 'hello 😱', + ); + }); + test('blob value', () { + expect(Reference.fromBuffer(b([3, 1, 2, 3, 3, 100, 1])).blobValue, [ + 1, + 2, + 3, + ]); + }); + test('bool vector', () { + var flx = Reference.fromBuffer(b([3, 1, 0, 1, 3, 144, 1])); + expect(flx[0].boolValue, true); + expect(flx[1].boolValue, false); + expect(flx[2].boolValue, true); + }); + test('number vector', () { + testNumbers([3, 1, 2, 3, 3, 44, 1], [1, 2, 3]); + testNumbers([3, 255, 2, 3, 3, 44, 1], [-1, 2, 3]); + testNumbers([3, 0, 1, 0, 43, 2, 3, 0, 6, 45, 1], [1, 555, 3]); + testNumbers( + [3, 0, 0, 0, 1, 0, 0, 0, 204, 216, 0, 0, 3, 0, 0, 0, 12, 46, 1], + [1, 55500, 3], + ); + testNumbers( + [ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 172, + 128, + 94, + 239, + 12, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 24, + 47, + 1, + ], + [1, 55555555500, 3], + ); + testNumbers( + [3, 0, 0, 0, 0, 0, 192, 63, 0, 0, 32, 64, 0, 0, 96, 64, 12, 54, 1], + [1.5, 2.5, 3.5], + ); + testNumbers( + [ + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 154, + 153, + 153, + 153, + 153, + 153, + 241, + 63, + 154, + 153, + 153, + 153, + 153, + 153, + 1, + 64, + 102, + 102, + 102, + 102, + 102, + 102, + 10, + 64, + 24, + 55, + 1, + ], + [1.1, 2.2, 3.3], + ); + }); + test('number vector, fixed type', () { + testNumbers([1, 2, 2, 64, 1], [1, 2]); + testNumbers([255, 255, 0, 1, 4, 65, 1], [-1, 256]); + testNumbers([211, 255, 255, 255, 0, 232, 3, 0, 8, 66, 1], [-45, 256000]); + testNumbers( + [ + 211, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 127, + 16, + 67, + 1, + ], + [-45, 9223372036854775807], + ); + + testNumbers([1, 2, 2, 68, 1], [1, 2]); + testNumbers([1, 0, 0, 1, 4, 69, 1], [1, 256]); + testNumbers([45, 0, 0, 0, 0, 232, 3, 0, 8, 70, 1], [45, 256000]); + + testNumbers([205, 204, 140, 63, 0, 0, 0, 192, 8, 74, 1], [1.1, -2]); + testNumbers( + [ + 154, + 153, + 153, + 153, + 153, + 153, + 241, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 112, + 192, + 16, + 75, + 1, + ], + [1.1, -256], + ); + + testNumbers( + [211, 255, 255, 255, 0, 232, 3, 0, 4, 0, 0, 0, 12, 78, 1], + [-45, 256000, 4], + ); + + testNumbers( + [ + 211, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 127, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 91, + 1, + ], + [-45, 9223372036854775807, 4, 9], + ); + + testNumbers( + [ + 45, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 127, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 95, + 1, + ], + [45, 9223372036854775807, 4, 9], + ); + + testNumbers( + [ + 154, + 153, + 153, + 153, + 153, + 153, + 241, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 112, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 16, + 64, + 24, + 87, + 1, + ], + [1.1, 256, 4], + ); + + testNumbers( + [ + 154, + 153, + 153, + 153, + 153, + 153, + 241, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 112, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 16, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 34, + 64, + 32, + 99, + 1, + ], + [1.1, 256, 4, 9], + ); + }); + test('string vector', () { + testStrings( + [ + 3, + 102, + 111, + 111, + 0, + 3, + 98, + 97, + 114, + 0, + 3, + 98, + 97, + 122, + 0, + 3, + 15, + 11, + 7, + 3, + 60, + 1, + ], + ['foo', 'bar', 'baz'], + ); + testStrings( + [ + 3, + 102, + 111, + 111, + 0, + 3, + 98, + 97, + 114, + 0, + 3, + 98, + 97, + 122, + 0, + 6, + 15, + 11, + 7, + 18, + 14, + 10, + 6, + 60, + 1, + ], + ['foo', 'bar', 'baz', 'foo', 'bar', 'baz'], + ); + }); + test('mixed vector', () { + var flx = Reference.fromBuffer( + b([ + 3, + 102, + 111, + 111, + 0, + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 251, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 205, + 204, + 204, + 204, + 204, + 204, + 244, + 63, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20, + 4, + 4, + 15, + 104, + 45, + 43, + 1, + ]), + ); + expect(flx.length, 5); + expect(flx[0].stringValue, 'foo'); + expect(flx[1].numValue, 1); + expect(flx[2].numValue, -5); + expect(flx[3].numValue, 1.3); + expect(flx[4].boolValue, true); + }); + + test('single value map', () { + var flx = Reference.fromBuffer(b([97, 0, 1, 3, 1, 1, 1, 12, 4, 2, 36, 1])); + expect(flx.length, 1); + expect(flx['a'].numValue, 12); + }); + test('two value map', () { + var flx = Reference.fromBuffer( + b([0, 97, 0, 2, 4, 4, 2, 1, 2, 45, 12, 4, 4, 4, 36, 1]), + ); + expect(flx.length, 2); + expect(flx['a'].numValue, 12); + expect(flx[''].numValue, 45); + }); + test('complex map', () { + var flx = complexMap(); + expect(flx.length, 5); + expect(flx['age'].numValue, 35); + expect(flx['weight'].numValue, 72.5); + expect(flx['name'].stringValue, 'Maxim'); + + expect(flx['flags'].length, 4); + expect(flx['flags'][0].boolValue, true); + expect(flx['flags'][1].boolValue, false); + expect(flx['flags'][2].boolValue, true); + expect(flx['flags'][3].boolValue, true); + + expect(flx['address'].length, 3); + expect(flx['address']['city'].stringValue, 'Bla'); + expect(flx['address']['zip'].stringValue, '12345'); + expect(flx['address']['countryCode'].stringValue, 'XX'); + + expect( + () => flx['address']['country'].stringValue, + throwsA( + predicate( + (dynamic e) => + e is ArgumentError && + e.message == + 'Key: [country] is not applicable on: //address of: ValueType.Map', + ), + ), + ); + expect( + () => flx['address']['countryCode'][0], + throwsA( + predicate( + (dynamic e) => + e is ArgumentError && + e.message == + 'Key: [0] is not applicable on: //address/countryCode of: ValueType.String', + ), + ), + ); + expect( + () => flx[1], + throwsA( + predicate( + (dynamic e) => + e is ArgumentError && + e.message == 'Key: [1] is not applicable on: / of: ValueType.Map', + ), + ), + ); + expect( + () => flx['flags'][4], + throwsA( + predicate( + (dynamic e) => + e is ArgumentError && + e.message == + 'Key: [4] is not applicable on: //flags of: ValueType.VectorBool length: 4', + ), + ), + ); + expect( + () => flx['flags'][-1], + throwsA( + predicate( + (dynamic e) => + e is ArgumentError && + e.message == + 'Key: [-1] is not applicable on: //flags of: ValueType.VectorBool length: 4', + ), + ), + ); + }); + test('complex map to json', () { + var flx = complexMap(); + expect( + flx.json, + '{"address":{"city":"Bla","countryCode":"XX","zip":"12345"},"age":35,"flags":[true,false,true,true],"name":"Maxim","weight":72.5}', + ); + }); + + test('complex map iterators', () { + var flx = complexMap(); + expect(flx.mapKeyIterable.map((e) => e).toList(), [ + 'address', + 'age', + 'flags', + 'name', + 'weight', + ]); + expect(flx.mapValueIterable.map((e) => e.json).toList(), [ + flx['address'].json, + flx['age'].json, + flx['flags'].json, + flx['name'].json, + flx['weight'].json, + ]); + expect(flx['flags'].vectorIterable.map((e) => e.boolValue).toList(), [ + true, + false, + true, + true, + ]); + }); + + test('bug where offest were stored as int instead of uint', () { + const data = [ + 99, + 104, + 97, + 110, + 110, + 101, + 108, + 115, + 95, + 105, + 110, + 0, + 100, + 105, + 108, + 97, + 116, + 105, + 111, + 110, + 95, + 104, + 101, + 105, + 103, + 104, + 116, + 95, + 102, + 97, + 99, + 116, + 111, + 114, + 0, + 100, + 105, + 108, + 97, + 116, + 105, + 111, + 110, + 95, + 119, + 105, + 100, + 116, + 104, + 95, + 102, + 97, + 99, + 116, + 111, + 114, + 0, + 102, + 117, + 115, + 101, + 100, + 95, + 97, + 99, + 116, + 105, + 118, + 97, + 116, + 105, + 111, + 110, + 95, + 102, + 117, + 110, + 99, + 116, + 105, + 111, + 110, + 0, + 112, + 97, + 100, + 95, + 118, + 97, + 108, + 117, + 101, + 115, + 0, + 112, + 97, + 100, + 100, + 105, + 110, + 103, + 0, + 115, + 116, + 114, + 105, + 100, + 101, + 95, + 104, + 101, + 105, + 103, + 104, + 116, + 0, + 115, + 116, + 114, + 105, + 100, + 101, + 95, + 119, + 105, + 100, + 116, + 104, + 0, + 8, + 130, + 119, + 97, + 76, + 51, + 41, + 34, + 21, + 8, + 1, + 8, + 64, + 1, + 1, + 1, + 1, + 0, + 1, + 1, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 16, + 36, + 1, + ]; + var flx = Reference.fromBuffer(b(data)); + expect( + flx.json, + '{"channels_in":64,"dilation_height_factor":1,"dilation_width_factor":1,"fused_activation_function":1,"pad_values":1,"padding":0,"stride_height":1,"stride_width":1}', + ); + const object = { + "channels_in": 64, + "dilation_height_factor": 1, + "dilation_width_factor": 1, + "fused_activation_function": 1, + "pad_values": 1, + "padding": 0, + "stride_height": 1, + "stride_width": 1, + }; + var data1 = Builder.buildFromObject(object).asUint8List(); + expect(data1.length, data.length); + var flx1 = Reference.fromBuffer(b(data1)); + expect( + flx1.json, + '{"channels_in":64,"dilation_height_factor":1,"dilation_width_factor":1,"fused_activation_function":1,"pad_values":1,"padding":0,"stride_height":1,"stride_width":1}', + ); + }); +} + +ByteBuffer b(List values) { + var data = Uint8List.fromList(values); + return data.buffer; +} + +void testNumbers(List buffer, List numbers) { + var flx = Reference.fromBuffer(b(buffer)); + expect(flx.length, numbers.length); + for (var i = 0; i < flx.length; i++) { + expect(flx[i].numValue, closeTo(numbers[i], 0.001)); + } +} + +void testStrings(List buffer, List numbers) { + var flx = Reference.fromBuffer(b(buffer)); + expect(flx.length, numbers.length); + for (var i = 0; i < flx.length; i++) { + expect(flx[i].stringValue, numbers[i]); + } +} + +Reference complexMap() { + // { + // "age": 35, + // "flags": [True, False, True, True], + // "weight": 72.5, + // "name": "Maxim", + // "address": { + // "city": "Bla", + // "zip": "12345", + // "countryCode": "XX", + // } + // } + return Reference.fromBuffer( + b([ + 97, + 100, + 100, + 114, + 101, + 115, + 115, + 0, + 99, + 105, + 116, + 121, + 0, + 3, + 66, + 108, + 97, + 0, + 99, + 111, + 117, + 110, + 116, + 114, + 121, + 67, + 111, + 100, + 101, + 0, + 2, + 88, + 88, + 0, + 122, + 105, + 112, + 0, + 5, + 49, + 50, + 51, + 52, + 53, + 0, + 3, + 38, + 29, + 14, + 3, + 1, + 3, + 38, + 22, + 15, + 20, + 20, + 20, + 97, + 103, + 101, + 0, + 102, + 108, + 97, + 103, + 115, + 0, + 4, + 1, + 0, + 1, + 1, + 110, + 97, + 109, + 101, + 0, + 5, + 77, + 97, + 120, + 105, + 109, + 0, + 119, + 101, + 105, + 103, + 104, + 116, + 0, + 5, + 93, + 36, + 33, + 23, + 12, + 0, + 0, + 7, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 35, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 45, + 0, + 0, + 0, + 0, + 0, + 145, + 66, + 36, + 4, + 144, + 20, + 14, + 25, + 38, + 1, + ]), + ); +} diff --git a/dart/test_preserve_case/flex_types_test.dart b/dart/test_preserve_case/flex_types_test.dart new file mode 100644 index 00000000000..7ed1f2ab1c1 --- /dev/null +++ b/dart/test_preserve_case/flex_types_test.dart @@ -0,0 +1,278 @@ +import 'package:flat_buffers/src/types.dart'; +import 'package:test/test.dart'; + +void main() { + test('is inline', () { + expect(ValueTypeUtils.isInline(ValueType.Bool), isTrue); + expect(ValueTypeUtils.isInline(ValueType.Int), isTrue); + expect(ValueTypeUtils.isInline(ValueType.UInt), isTrue); + expect(ValueTypeUtils.isInline(ValueType.Float), isTrue); + expect(ValueTypeUtils.isInline(ValueType.Null), isTrue); + expect(ValueTypeUtils.isInline(ValueType.String), isFalse); + }); + test('is type vector element', () { + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Bool), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Int), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.UInt), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Float), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Key), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.String), isTrue); + + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Null), isFalse); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Blob), isFalse); + }); + test('is typed vector', () { + expect(ValueTypeUtils.isTypedVector(ValueType.VectorInt), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorUInt), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorFloat), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorBool), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorKey), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorString), isTrue); + + expect(ValueTypeUtils.isTypedVector(ValueType.Vector), isFalse); + expect(ValueTypeUtils.isTypedVector(ValueType.Map), isFalse); + expect(ValueTypeUtils.isTypedVector(ValueType.Bool), isFalse); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorInt2), isFalse); + }); + test('is fixed typed vector', () { + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt2), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt3), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt4), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt2), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt3), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt4), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat2), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat3), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat4), isTrue); + + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt), isFalse); + }); + test('to typed vector', () { + expect( + ValueTypeUtils.toTypedVector(ValueType.Int, 0), + equals(ValueType.VectorInt), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.UInt, 0), + equals(ValueType.VectorUInt), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.Bool, 0), + equals(ValueType.VectorBool), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.Float, 0), + equals(ValueType.VectorFloat), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.Key, 0), + equals(ValueType.VectorKey), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.String, 0), + equals(ValueType.VectorString), + ); + + expect( + ValueTypeUtils.toTypedVector(ValueType.Int, 2), + equals(ValueType.VectorInt2), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.UInt, 2), + equals(ValueType.VectorUInt2), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.Float, 2), + equals(ValueType.VectorFloat2), + ); + + expect( + ValueTypeUtils.toTypedVector(ValueType.Int, 3), + equals(ValueType.VectorInt3), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.UInt, 3), + equals(ValueType.VectorUInt3), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.Float, 3), + equals(ValueType.VectorFloat3), + ); + + expect( + ValueTypeUtils.toTypedVector(ValueType.Int, 4), + equals(ValueType.VectorInt4), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.UInt, 4), + equals(ValueType.VectorUInt4), + ); + expect( + ValueTypeUtils.toTypedVector(ValueType.Float, 4), + equals(ValueType.VectorFloat4), + ); + }); + test('typed vector element type', () { + expect( + ValueTypeUtils.typedVectorElementType(ValueType.VectorInt), + equals(ValueType.Int), + ); + expect( + ValueTypeUtils.typedVectorElementType(ValueType.VectorUInt), + equals(ValueType.UInt), + ); + expect( + ValueTypeUtils.typedVectorElementType(ValueType.VectorFloat), + equals(ValueType.Float), + ); + expect( + ValueTypeUtils.typedVectorElementType(ValueType.VectorString), + equals(ValueType.String), + ); + expect( + ValueTypeUtils.typedVectorElementType(ValueType.VectorKey), + equals(ValueType.Key), + ); + expect( + ValueTypeUtils.typedVectorElementType(ValueType.VectorBool), + equals(ValueType.Bool), + ); + }); + test('fixed typed vector element type', () { + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt2), + equals(ValueType.Int), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt3), + equals(ValueType.Int), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt4), + equals(ValueType.Int), + ); + + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt2), + equals(ValueType.UInt), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt3), + equals(ValueType.UInt), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt4), + equals(ValueType.UInt), + ); + + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat2), + equals(ValueType.Float), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat3), + equals(ValueType.Float), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat4), + equals(ValueType.Float), + ); + }); + test('fixed typed vector element size', () { + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt2), + equals(2), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt3), + equals(3), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt4), + equals(4), + ); + + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt2), + equals(2), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt3), + equals(3), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt4), + equals(4), + ); + + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat2), + equals(2), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat3), + equals(3), + ); + expect( + ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat4), + equals(4), + ); + }); + test('packed type', () { + expect( + ValueTypeUtils.packedType(ValueType.Null, BitWidth.width8), + equals(0), + ); + expect( + ValueTypeUtils.packedType(ValueType.Null, BitWidth.width16), + equals(1), + ); + expect( + ValueTypeUtils.packedType(ValueType.Null, BitWidth.width32), + equals(2), + ); + expect( + ValueTypeUtils.packedType(ValueType.Null, BitWidth.width64), + equals(3), + ); + + expect( + ValueTypeUtils.packedType(ValueType.Int, BitWidth.width8), + equals(4), + ); + expect( + ValueTypeUtils.packedType(ValueType.Int, BitWidth.width16), + equals(5), + ); + expect( + ValueTypeUtils.packedType(ValueType.Int, BitWidth.width32), + equals(6), + ); + expect( + ValueTypeUtils.packedType(ValueType.Int, BitWidth.width64), + equals(7), + ); + }); + test('bit width', () { + expect(BitWidthUtil.width(0), BitWidth.width8); + expect(BitWidthUtil.width(-20), BitWidth.width8); + expect(BitWidthUtil.width(127), BitWidth.width8); + expect(BitWidthUtil.width(128), BitWidth.width16); + expect(BitWidthUtil.width(128123), BitWidth.width32); + expect(BitWidthUtil.width(12812324534), BitWidth.width64); + expect(BitWidthUtil.width(-127), BitWidth.width8); + expect(BitWidthUtil.width(-128), BitWidth.width16); + expect(BitWidthUtil.width(-12812324534), BitWidth.width64); + expect(BitWidthUtil.width(-0.1), BitWidth.width64); + expect(BitWidthUtil.width(0.25), BitWidth.width32); + }); + test('padding size', () { + expect(BitWidthUtil.paddingSize(10, 8), 6); + expect(BitWidthUtil.paddingSize(10, 4), 2); + expect(BitWidthUtil.paddingSize(15, 4), 1); + expect(BitWidthUtil.paddingSize(15, 2), 1); + expect(BitWidthUtil.paddingSize(15, 1), 0); + expect(BitWidthUtil.paddingSize(16, 8), 0); + expect(BitWidthUtil.paddingSize(17, 8), 7); + }); +} diff --git a/dart/test_preserve_case/monster_test.fbs b/dart/test_preserve_case/monster_test.fbs new file mode 100644 index 00000000000..b40ecf58f95 --- /dev/null +++ b/dart/test_preserve_case/monster_test.fbs @@ -0,0 +1,180 @@ +// test schema file + +include "include_test1.fbs"; + +namespace MyGame; + +table InParentNamespace {} + +namespace MyGame.Example2; + +table Monster {} // Test having same name as below, but in different namespace. + +namespace MyGame.Example; + +attribute "priority"; + +/// Composite components of Monster color. +enum Color:ubyte (bit_flags) { + Red = 0, // color Red = (1u << 0) + /// \brief color Green + /// Green is bit_flag with value (1u << 1) + Green, + /// \brief color Blue (1u << 3) + Blue = 3, +} + +enum Race:byte { + None = -1, + Human = 0, + Dwarf, + Elf, +} + +enum LongEnum:ulong (bit_flags) { + LongOne = 1, + LongTwo = 2, + // Because this is a bitflag, 40 will be out of range of a 32-bit integer, + // allowing us to exercise any logic special to big numbers. + LongBig = 40, +} + +union Any { Monster, TestSimpleTableWithEnum, MyGame.Example2.Monster } + +union AnyUniqueAliases { M: Monster, TS: TestSimpleTableWithEnum, M2: MyGame.Example2.Monster } +union AnyAmbiguousAliases { M1: Monster, M2: Monster, M3: Monster } + +struct Test { a:short; b:byte; } + +table TestSimpleTableWithEnum (csharp_partial, private) { + color: Color = Green; +} + +struct Vec3 (force_align: 8) { + x:float; + y:float; + z:float; + test1:double; + test2:Color; + test3:Test; +} + +struct Ability { + id:uint(key); + distance:uint; +} + +struct StructOfStructs { + a: Ability; + b: Test; + c: Ability; +} + +struct StructOfStructsOfStructs { + a: StructOfStructs; +} + +table Stat { + id:string; + val:long; + count:ushort (key); +} + +table Referrable { + id:ulong(key, hash:"fnv1a_64"); +} + +/// an example documentation comment: "monster object" +table Monster { + pos:Vec3 (id: 0); + hp:short = 100 (id: 2); + mana:short = 150 (id: 1); + name:string (id: 3, key); + color:Color = Blue (id: 6); + inventory:[ubyte] (id: 5); + friendly:bool = false (deprecated, priority: 1, id: 4); + /// an example documentation comment: this will end up in the generated code + /// multiline too + testarrayoftables:[Monster] (id: 11); + testarrayofstring:[string] (id: 10); + testarrayofstring2:[string] (id: 28); + testarrayofbools:[bool] (id: 24); + testarrayofsortedstruct:[Ability] (id: 29); + enemy:MyGame.Example.Monster (id:12); // Test referring by full namespace. + test:Any (id: 8); + test4:[Test] (id: 9); + test5:[Test] (id: 31); + testnestedflatbuffer:[ubyte] (id:13, nested_flatbuffer: "Monster"); + testempty:Stat (id:14); + testbool:bool (id:15); + testhashs32_fnv1:int (id:16, hash:"fnv1_32"); + testhashu32_fnv1:uint (id:17, hash:"fnv1_32"); + testhashs64_fnv1:long (id:18, hash:"fnv1_64"); + testhashu64_fnv1:ulong (id:19, hash:"fnv1_64"); + testhashs32_fnv1a:int (id:20, hash:"fnv1a_32"); + testhashu32_fnv1a:uint (id:21, hash:"fnv1a_32", cpp_type:"Stat"); + testhashs64_fnv1a:long (id:22, hash:"fnv1a_64"); + testhashu64_fnv1a:ulong (id:23, hash:"fnv1a_64"); + testf:float = 3.14159 (id:25); + testf2:float = 3 (id:26); + testf3:float (id:27); + flex:[ubyte] (id:30, flexbuffer); + vector_of_longs:[long] (id:32); + vector_of_doubles:[double] (id:33); + parent_namespace_test:InParentNamespace (id:34); + vector_of_referrables:[Referrable](id:35); + single_weak_reference:ulong(id:36, hash:"fnv1a_64", cpp_type:"ReferrableT"); + vector_of_weak_references:[ulong](id:37, hash:"fnv1a_64", cpp_type:"ReferrableT"); + vector_of_strong_referrables:[Referrable](id:38, cpp_ptr_type:"default_ptr_type"); //was shared_ptr + co_owning_reference:ulong(id:39, hash:"fnv1a_64", cpp_type:"ReferrableT", cpp_ptr_type:"naked"); //was shared_ptr as well + vector_of_co_owning_references:[ulong](id:40, hash:"fnv1a_64", cpp_type:"ReferrableT", cpp_ptr_type:"default_ptr_type", cpp_ptr_type_get:".get()"); //was shared_ptr + non_owning_reference:ulong(id:41, hash:"fnv1a_64", cpp_type:"ReferrableT", cpp_ptr_type:"naked", cpp_ptr_type_get:""); //was weak_ptr + vector_of_non_owning_references:[ulong](id:42, hash:"fnv1a_64", cpp_type:"ReferrableT", cpp_ptr_type:"naked", cpp_ptr_type_get:""); //was weak_ptr + any_unique:AnyUniqueAliases(id:44); + any_ambiguous:AnyAmbiguousAliases (id:46); + vector_of_enums:[Color] (id:47); + signed_enum:Race = None (id:48); + testrequirednestedflatbuffer:[ubyte] (id:49, nested_flatbuffer: "Monster"); + scalar_key_sorted_tables:[Stat] (id: 50); + native_inline:Test (id: 51, native_inline); + // The default value of this enum will be a numeric zero, which isn't a valid + // enum value. + long_enum_non_enum_default:LongEnum (id: 52); + long_enum_normal_default:LongEnum = LongOne (id: 53); + // Test that default values nan and +/-inf work. + nan_default:float = nan (id: 54); + inf_default:float = inf (id: 55); + positive_inf_default:float = +inf (id: 56); + infinity_default:float = infinity (id: 57); + positive_infinity_default:float = +infinity (id: 58); + negative_inf_default:float = -inf (id: 59); + negative_infinity_default:float = -infinity (id: 60); + double_inf_default:double = inf (id: 61); +} + +table TypeAliases { + i8:int8; + u8:uint8; + i16:int16; + u16:uint16; + i32:int32; + u32:uint32; + i64:int64; + u64:uint64; + f32:float32; + f64:float64; + v8:[int8]; + vf64:[float64]; +} + +rpc_service MonsterStorage { + Store(Monster):Stat (streaming: "none"); + Retrieve(Stat):Monster (streaming: "server", idempotent); + GetMaxHitPoint(Monster):Stat (streaming: "client"); + GetMinMaxHitPoints(Monster):Stat (streaming: "bidi"); +} + +root_type Monster; + +file_identifier "MONS"; +file_extension "mon"; diff --git a/dart/test_preserve_case/monsterdata_test.mon b/dart/test_preserve_case/monsterdata_test.mon new file mode 100644 index 0000000000000000000000000000000000000000..da0ed8698fcafc71115525dc6fe40fead48515d1 GIT binary patch literal 600 zcmZ{hy-EX75QWcfepo{wDPn7@MFf=;7J};!lEP>}3kwSi7ZZq4vs)xsm~^QGA&-#4 z(uWYl!Z%1jpFk8Wtk!dGb|nZ-^39w(Gr2RnX#=S2?;S9pfdL%GG>Y^~B#9DMu}0+* zAcbv|afvo|QAHPh9Gbk`5jn;QPJc81i5e$0ShTDveRrMC<(I_fxHe~{qvOy52Bb*x zWW@X<*?8`Z#nrHPEAE-=lUGuTdi5k0Z1nD~KVQ1JW*k4pXHTQ@_f{O)#xCAZ7Iffr zhtIDak`d>1-v=@%u##mZqc+~2rJraZL0zi{CaStfxC&-Til|pT7C#SR7{+(+v^?|T zv_pX?ul~UEgOw@0OsyVmF~?1(b=LfQEb`Zy^Y?De> generators; }; diff --git a/include/flatbuffers/idlnames.h b/include/flatbuffers/idlnames.h new file mode 100644 index 00000000000..39342c0cb0f --- /dev/null +++ b/include/flatbuffers/idlnames.h @@ -0,0 +1,41 @@ +#ifndef FLATBUFFERS_INCLUDE_CODEGEN_IDLNAMES_H_ +#define FLATBUFFERS_INCLUDE_CODEGEN_IDLNAMES_H_ + +#include +#include + +namespace flatbuffers { + +// Hash + equality on pointer identity, not string content +struct PtrHash { + size_t operator()(const std::string* s) const noexcept { + return std::hash()(static_cast(s)); + } +}; + +struct PtrEqual { + bool operator()(const std::string* a, const std::string* b) const noexcept { + return a == b; + } +}; + +inline std::unordered_set& +IdlNamePointers() { + static std::unordered_set pointers; + return pointers; +} + +// Record the address of an IDL name (struct name, field name, enum value, +// etc.). +inline void RecordIdlName(const std::string* name) { + IdlNamePointers().insert(name); +} + +// Test whether a given string object comes from the IDL. +inline bool IsIdlName(const std::string& s) { + return IdlNamePointers().count(&s) != 0; +} + +} // namespace flatbuffers + +#endif // FLATBUFFERS_INCLUDE_CODEGEN_IDLNAMES_H_ diff --git a/include/flatbuffers/options.h b/include/flatbuffers/options.h new file mode 100644 index 00000000000..29e884b9d7c --- /dev/null +++ b/include/flatbuffers/options.h @@ -0,0 +1,12 @@ +#ifndef FLATBUFFERS_FLATC_OPTIONS_GLOBAL_H_ +#define FLATBUFFERS_FLATC_OPTIONS_GLOBAL_H_ + +#include "flatc.h" + +namespace flatbuffers { + +extern FlatCOptions global_options; + +} + +#endif // FLATBUFFERS_FLATC_OPTIONS_GLOBAL_H_ diff --git a/src/BUILD.bazel b/src/BUILD.bazel index 4b9fb7a8156..41e8891d1a3 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -42,6 +42,7 @@ cc_library( "idl_gen_text.cpp", "idl_gen_text.h", "idl_parser.cpp", + "options.cpp", "reflection.cpp", "util.cpp", ], diff --git a/src/flatc.cpp b/src/flatc.cpp index 612f797cc18..9ba5c1f9a26 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -17,6 +17,7 @@ #include "flatbuffers/flatc.h" #include +#include #include #include #include @@ -261,6 +262,7 @@ const static FlatCOption flatc_options[] = { "Currently this is required to generate private types in Rust"}, {"", "python-no-type-prefix-suffix", "", "Skip emission of Python functions that are prefixed with typenames"}, + {"", "preserve-case", "", "Preserve all property cases as defined in IDL"}, {"", "python-typing", "", "Generate Python type annotations"}, {"", "python-version", "", "Generate code for the given Python version."}, {"", "python-decode-obj-api-strings", "", @@ -655,6 +657,8 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc, opts.set_empty_vectors_to_null = false; } else if (arg == "--force-empty-vectors") { opts.set_empty_vectors_to_null = false; + } else if (arg == "--preserve-case") { + options.preserve_case = true; } else if (arg == "--java-primitive-has-method") { opts.java_primitive_has_method = true; } else if (arg == "--cs-gen-json-serializer") { @@ -760,6 +764,38 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc, } auto code_generator_it = code_generators_.find(arg); + + if (code_generator_it != code_generators_.end()) { + options.generators.push_back(code_generator_it->second); + if (options.preserve_case) { + static const std::set preserve_case_supported = { + "dart", "cpp", "go", "php", "python", + "ts", "jsonschema", "rust", "java"}; + std::string matched_lang = arg; + if (matched_lang.rfind("--", 0) == 0) + matched_lang = matched_lang.substr(2); + else if (matched_lang.rfind("-", 0) == 0) + matched_lang = matched_lang.substr(1); + static const std::map short_to_lang = { + {"b", "binary"}, {"c", "cpp"}, {"n", "csharp"}, {"d", "dart"}, + {"g", "go"}, {"j", "java"}, {"t", "json"}, {"l", "lua"}, + {"p", "python"}, {"r", "rust"}, {"T", "ts"}}; + auto it_lang = short_to_lang.find(matched_lang); + if (it_lang != short_to_lang.end()) matched_lang = it_lang->second; + if (!preserve_case_supported.count(matched_lang)) { + fprintf(stderr, + "[FlatBuffers] --preserve-case is not currently " + "supported for '%s' (%s).\n" + "Pull requests are welcome at: " + "https://github.com/google/flatbuffers\n", + matched_lang.c_str(), arg.c_str()); + } + } + } else { + Error("unknown commandline argument: " + arg, true, true); + return options; + } + if (code_generator_it == code_generators_.end()) { Error("unknown commandline argument: " + arg, true); return options; diff --git a/src/flatc_main.cpp b/src/flatc_main.cpp index bcbb55d8376..f9d3e33c1d6 100644 --- a/src/flatc_main.cpp +++ b/src/flatc_main.cpp @@ -22,6 +22,7 @@ #include "flatbuffers/base.h" #include "flatbuffers/code_generator.h" #include "flatbuffers/flatc.h" +#include "flatbuffers/options.h" #include "flatbuffers/util.h" #include "idl_gen_binary.h" #include "idl_gen_cpp.h" @@ -180,10 +181,9 @@ int main(int argc, const char* argv[]) { "Generate TypeScript code for tables/structs"}, flatbuffers::NewTsCodeGenerator()); - // Create the FlatC options by parsing the command line arguments. - const flatbuffers::FlatCOptions& options = - flatc.ParseFromCommandLineArguments(argc, argv); + // Parse command-line arguments and assign to global_options + flatbuffers::global_options = flatc.ParseFromCommandLineArguments(argc, argv); // Compile with the extracted FlatC options. - return flatc.Compile(options); + return flatc.Compile(flatbuffers::global_options); } diff --git a/src/idl_gen_dart.cpp b/src/idl_gen_dart.cpp index 52fc8772f24..221e73c5ec1 100644 --- a/src/idl_gen_dart.cpp +++ b/src/idl_gen_dart.cpp @@ -18,6 +18,7 @@ #include "idl_gen_dart.h" #include +#include #include #include "flatbuffers/code_generators.h" @@ -90,6 +91,46 @@ class DartGenerator : public BaseGenerator { namer_(WithFlagOptions(DartDefaultConfig(), parser.opts, path), DartKeywords()) {} + std::string SnakeToCamel(const std::string& value, bool upper_first) const { + if (value.find('_') == std::string::npos) { + return value; + } + std::string result; + result.reserve(value.size()); + bool capitalize = upper_first; + for (char ch : value) { + if (ch == '_') { + capitalize = true; + continue; + } + unsigned char uch = static_cast(ch); + if (capitalize) { + result.push_back(static_cast(std::toupper(uch))); + capitalize = false; + } else { + result.push_back(static_cast(std::tolower(uch))); + } + } + if (!upper_first && !result.empty()) { + result[0] = static_cast( + std::tolower(static_cast(result[0]))); + } + return result; + } + + std::string CompatFieldLower(const FieldDef& field) const { + return SnakeToCamel(field.name, false); + } + + std::string CompatStructName(const StructDef& struct_def) const { + return SnakeToCamel(struct_def.name, true); + } + + bool NeedsCompat(const std::string& original, + const std::string& compat) const { + return original != compat; + } + template void import_generator(const std::vector& definitions, const std::string& included, @@ -469,6 +510,10 @@ class DartGenerator : public BaseGenerator { namespace_code[namer_.Namespace(*struct_def.defined_namespace)]; const auto& struct_type = namer_.Type(struct_def); + const std::string compat_struct = CompatStructName(struct_def); + const std::string compat_object = compat_struct + "T"; + const std::string compat_builder = compat_struct + "Builder"; + const std::string compat_object_builder = compat_struct + "ObjectBuilder"; // Emit constructor @@ -533,6 +578,23 @@ class DartGenerator : public BaseGenerator { code += reader_code; code += builder_code; + + if (NeedsCompat(struct_type, compat_struct)) { + code += "typedef " + compat_struct + " = " + struct_type + ";\n\n"; + } + if (parser_.opts.generate_object_based_api) { + const std::string object_type = namer_.ObjectType(struct_def); + if (NeedsCompat(object_type, compat_object)) { + code += "typedef " + compat_object + " = " + object_type + ";\n\n"; + } + if (NeedsCompat(object_builder_name, compat_object_builder)) { + code += "typedef " + compat_object_builder + " = " + + object_builder_name + ";\n\n"; + } + } + if (NeedsCompat(builder_name, compat_builder)) { + code += "typedef " + compat_builder + " = " + builder_name + ";\n\n"; + } } // Generate an accessor struct with constructor for a flatbuffers struct. @@ -551,6 +613,7 @@ class DartGenerator : public BaseGenerator { const FieldDef& field = *it->second; const std::string field_name = namer_.Field(field); + const std::string compat_name = CompatFieldLower(field); const std::string defaultValue = getDefaultValue(field.value); const std::string type_name = GenDartTypeName(field.value.type, struct_def.defined_namespace, field, @@ -559,6 +622,28 @@ class DartGenerator : public BaseGenerator { GenDocComment(field.doc_comment, " ", code); code += " " + type_name + " " + field_name + ";\n"; + if (NeedsCompat(field_name, compat_name)) { + code += " " + type_name + " get " + compat_name + " => " + field_name + + ";\n"; + code += " set " + compat_name + "(" + type_name + " value) => " + + field_name + " = value;\n"; + } + const std::string type_suffix = "_type"; + if (field_name.size() > type_suffix.size() && + field_name.compare(field_name.size() - type_suffix.size(), + type_suffix.size(), type_suffix) == 0) { + const std::string hybrid_alias = + field_name.substr(0, field_name.size() - type_suffix.size()) + + "Type"; + if (NeedsCompat(field_name, hybrid_alias) && + hybrid_alias != compat_name) { + code += " " + type_name + " get " + hybrid_alias + " => " + + field_name + ";\n"; + code += " set " + hybrid_alias + "(" + type_name + " value) => " + + field_name + " = value;\n"; + } + } + if (!constructor_args.empty()) constructor_args += ",\n"; constructor_args += " "; constructor_args += (struct_def.fixed ? "required " : ""); @@ -691,6 +776,7 @@ class DartGenerator : public BaseGenerator { const FieldDef& field = *it->second; const std::string field_name = namer_.Field(field); + const std::string compat_name = CompatFieldLower(field); const std::string defaultValue = getDefaultValue(field.value); const bool isNullable = defaultValue.empty() && !struct_def.fixed; const std::string type_name = @@ -745,6 +831,24 @@ class DartGenerator : public BaseGenerator { } code += ";\n"; } + + if (NeedsCompat(field_name, compat_name)) { + code += " " + type_name + " get " + compat_name + " => " + field_name + + ";\n"; + } + const std::string type_suffix = "_type"; + if (field_name.size() > type_suffix.size() && + field_name.compare(field_name.size() - type_suffix.size(), + type_suffix.size(), type_suffix) == 0) { + const std::string hybrid_alias = + field_name.substr(0, field_name.size() - type_suffix.size()) + + "Type"; + if (NeedsCompat(field_name, hybrid_alias) && + hybrid_alias != compat_name) { + code += " " + type_name + " get " + hybrid_alias + " => " + + field_name + ";\n"; + } + } } code += "\n"; @@ -760,15 +864,19 @@ class DartGenerator : public BaseGenerator { code += " return '" + object_name + "{"; for (auto it = non_deprecated_fields.begin(); it != non_deprecated_fields.end(); ++it) { - const std::string field = namer_.Field(*it->second); + const FieldDef& field_def = *it->second; + const std::string field = namer_.Field(field_def); + const std::string compat = CompatFieldLower(field_def); + const std::string display = NeedsCompat(field, compat) ? compat : field; // We need to escape the fact that some fields have $ in the name which is // also used in symbol/string substitution. std::string escaped_field; - for (size_t i = 0; i < field.size(); i++) { - if (field[i] == '$') escaped_field.push_back('\\'); - escaped_field.push_back(field[i]); + for (size_t i = 0; i < display.size(); i++) { + if (display[i] == '$') escaped_field.push_back('\\'); + escaped_field.push_back(display[i]); } - code += escaped_field + ": ${" + field + "}"; + const std::string accessor = NeedsCompat(field, compat) ? compat : field; + code += escaped_field + ": ${" + accessor + "}"; if (it != non_deprecated_fields.end() - 1) { code += ", "; } @@ -964,11 +1072,21 @@ class DartGenerator : public BaseGenerator { it != non_deprecated_fields.end(); ++it) { const FieldDef& field = *it->second; + const std::string variable_name = namer_.Variable(field); + const std::string compat_variable = CompatFieldLower(field); + const bool needs_alias = NeedsCompat(variable_name, compat_variable); + const std::string type_expr = + GenDartTypeName(field.value.type, struct_def.defined_namespace, + field, !struct_def.fixed, "ObjectBuilder"); + code += " "; - code += (struct_def.fixed ? "required " : "") + - GenDartTypeName(field.value.type, struct_def.defined_namespace, - field, !struct_def.fixed, "ObjectBuilder") + - " " + namer_.Variable(field) + ",\n"; + if (struct_def.fixed && !needs_alias) code += "required "; + code += type_expr + " " + variable_name + ",\n"; + if (needs_alias) { + std::string alias_type = type_expr; + if (alias_type.find('?') == std::string::npos) alias_type += "?"; + code += " " + alias_type + " " + compat_variable + ",\n"; + } } code += " })\n"; code += " : "; @@ -976,7 +1094,16 @@ class DartGenerator : public BaseGenerator { it != non_deprecated_fields.end(); ++it) { const FieldDef& field = *it->second; - code += "_" + namer_.Variable(field) + " = " + namer_.Variable(field); + const std::string variable_name = namer_.Variable(field); + const std::string compat_variable = CompatFieldLower(field); + const bool needs_alias = NeedsCompat(variable_name, compat_variable); + + code += "_" + variable_name + " = "; + if (needs_alias) { + code += compat_variable + " ?? " + variable_name; + } else { + code += variable_name; + } if (it == non_deprecated_fields.end() - 1) { code += ";\n\n"; } else { diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp index f4a03cec446..8844a04ebfc 100644 --- a/src/idl_gen_go.cpp +++ b/src/idl_gen_go.cpp @@ -19,6 +19,7 @@ #include "idl_gen_go.h" #include +#include #include #include #include @@ -27,6 +28,7 @@ #include "flatbuffers/code_generators.h" #include "flatbuffers/flatbuffers.h" #include "flatbuffers/idl.h" +#include "flatbuffers/options.h" #include "flatbuffers/util.h" #include "idl_namer.h" @@ -397,11 +399,21 @@ class GoGenerator : public BaseGenerator { std::string* code_ptr) { std::string& code = *code_ptr; + const std::string base_name = namer_.Function(field) + "Length"; + const std::string compat_name = CompatFieldUpper(field) + "Length"; + GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field) + "Length("; + code += " " + base_name + "("; code += ") int " + OffsetPrefix(field); code += "\t\treturn rcv._tab.VectorLen(o)\n\t}\n"; code += "\treturn 0\n}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "() int {\n"; + code += "\treturn rcv." + base_name + "()\n"; + code += "}\n\n"; + } } // Get a [ubyte] vector as a byte slice. @@ -409,11 +421,21 @@ class GoGenerator : public BaseGenerator { std::string* code_ptr) { std::string& code = *code_ptr; + const std::string base_name = namer_.Function(field) + "Bytes"; + const std::string compat_name = CompatFieldUpper(field) + "Bytes"; + GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field) + "Bytes("; + code += " " + base_name + "("; code += ") []byte " + OffsetPrefix(field); code += "\t\treturn rcv._tab.ByteVector(o + rcv._tab.Pos)\n\t}\n"; code += "\treturn nil\n}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "() []byte {\n"; + code += "\treturn rcv." + base_name + "()\n"; + code += "}\n\n"; + } } // Get the value of a struct's scalar. @@ -422,13 +444,22 @@ class GoGenerator : public BaseGenerator { std::string& code = *code_ptr; std::string getter = GenGetter(field.value.type); GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + code += " " + base_name; code += "() " + TypeName(field) + " {\n"; code += "\treturn " + CastToEnum(field.value.type, getter + "(rcv._tab.Pos + flatbuffers.UOffsetT(" + NumToString(field.value.offset) + "))"); code += "\n}\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "\nfunc (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "() " + TypeName(field) + " {\n"; + code += "\treturn rcv." + base_name + "()\n"; + code += "}\n"; + } } // Get the value of a table's scalar. @@ -437,7 +468,9 @@ class GoGenerator : public BaseGenerator { std::string& code = *code_ptr; std::string getter = GenGetter(field.value.type); GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + code += " " + base_name; code += "() " + TypeName(field) + " "; code += OffsetPrefix(field); if (field.IsScalarOptional()) { @@ -452,6 +485,13 @@ class GoGenerator : public BaseGenerator { code += "\n\t}\n"; code += "\treturn " + GenConstant(field) + "\n"; code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "() " + TypeName(field) + " {\n"; + code += "\treturn rcv." + base_name + "()\n"; + code += "}\n\n"; + } } // Get a struct by initializing an existing struct. @@ -460,7 +500,9 @@ class GoGenerator : public BaseGenerator { const FieldDef& field, std::string* code_ptr) { std::string& code = *code_ptr; GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + code += " " + base_name; code += "(obj *" + TypeName(field); code += ") *" + TypeName(field); code += " {\n"; @@ -471,6 +513,13 @@ class GoGenerator : public BaseGenerator { code += NumToString(field.value.offset) + ")"; code += "\n\treturn obj\n"; code += "}\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "\nfunc (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "(obj *" + TypeName(field) + ") *" + TypeName(field) + " {\n"; + code += "\treturn rcv." + base_name + "(obj)\n"; + code += "}\n"; + } } // Get a struct by initializing an existing struct. @@ -479,7 +528,9 @@ class GoGenerator : public BaseGenerator { std::string* code_ptr) { std::string& code = *code_ptr; GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + code += " " + base_name; code += "(obj *"; code += TypeName(field); code += ") *" + TypeName(field) + " " + OffsetPrefix(field); @@ -494,6 +545,13 @@ class GoGenerator : public BaseGenerator { code += "\t\tobj.Init(rcv._tab.Bytes, x)\n"; code += "\t\treturn obj\n\t}\n\treturn nil\n"; code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "(obj *" + TypeName(field) + ") *" + TypeName(field) + " {\n"; + code += "\treturn rcv." + base_name + "(obj)\n"; + code += "}\n\n"; + } } // Get the value of a string. @@ -501,11 +559,20 @@ class GoGenerator : public BaseGenerator { std::string* code_ptr) { std::string& code = *code_ptr; GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + code += " " + base_name; code += "() " + TypeName(field) + " "; code += OffsetPrefix(field) + "\t\treturn " + GenGetter(field.value.type); code += "(o + rcv._tab.Pos)\n\t}\n\treturn nil\n"; code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "() " + TypeName(field) + " {\n"; + code += "\treturn rcv." + base_name + "()\n"; + code += "}\n\n"; + } } // Get the value of a union from an object. @@ -513,13 +580,22 @@ class GoGenerator : public BaseGenerator { std::string* code_ptr) { std::string& code = *code_ptr; GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field) + "("; + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + code += " " + base_name + "("; code += "obj " + GenTypePointer(field.value.type) + ") bool "; code += OffsetPrefix(field); code += "\t\t" + GenGetter(field.value.type); code += "(obj, o)\n\t\treturn true\n\t}\n"; code += "\treturn false\n"; code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "(obj " + GenTypePointer(field.value.type) + ") bool {\n"; + code += "\treturn rcv." + base_name + "(obj)\n"; + code += "}\n\n"; + } } // Get the value of a vector's struct member. @@ -528,8 +604,11 @@ class GoGenerator : public BaseGenerator { std::string& code = *code_ptr; auto vectortype = field.value.type.VectorType(); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + code += " " + base_name; code += "(obj *" + TypeName(field); code += ", j int) bool " + OffsetPrefix(field); code += "\t\tx := rcv._tab.Vector(o)\n"; @@ -538,10 +617,20 @@ class GoGenerator : public BaseGenerator { if (!(vectortype.struct_def->fixed)) { code += "\t\tx = rcv._tab.Indirect(x)\n"; } + code += "\t\tif obj == nil {\n"; + code += "\t\t\tobj = new(" + TypeName(field) + ")\n"; + code += "\t\t}\n"; code += "\t\tobj.Init(rcv._tab.Bytes, x)\n"; code += "\t\treturn true\n\t}\n"; code += "\treturn false\n"; code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "(obj *" + TypeName(field) + ", j int) bool {\n"; + code += "\treturn rcv." + base_name + "(obj, j)\n"; + code += "}\n\n"; + } } void GetMemberOfVectorOfStructByKey(const StructDef& struct_def, @@ -561,8 +650,11 @@ class GoGenerator : public BaseGenerator { auto& key_field = **kit; FLATBUFFERS_ASSERT(key_field.key); + const std::string base_name = namer_.Field(field) + "ByKey"; + const std::string compat_name = CompatFieldUpper(field) + "ByKey"; + GenReceiver(struct_def, code_ptr); - code += " " + namer_.Field(field) + "ByKey"; + code += " " + base_name; code += "(obj *" + TypeName(field); code += ", key " + NativeType(key_field.value.type) + ") bool " + OffsetPrefix(field); @@ -572,6 +664,14 @@ class GoGenerator : public BaseGenerator { code += "\t}\n"; code += "\treturn false\n"; code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "(obj *" + TypeName(field) + ", key " + + NativeType(key_field.value.type) + ") bool {\n"; + code += "\treturn rcv." + base_name + "(obj, key)\n"; + code += "}\n\n"; + } } // Get the value of a vector's non-struct member. @@ -581,8 +681,11 @@ class GoGenerator : public BaseGenerator { std::string& code = *code_ptr; auto vectortype = field.value.type.VectorType(); + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + GenReceiver(struct_def, code_ptr); - code += " " + namer_.Function(field); + code += " " + base_name; code += "(j int) " + TypeName(field) + " "; code += OffsetPrefix(field); code += "\t\ta := rcv._tab.Vector(o)\n"; @@ -600,6 +703,13 @@ class GoGenerator : public BaseGenerator { code += "\treturn 0\n"; } code += "}\n\n"; + + if (NeedsCompat(base_name, compat_name)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") " + compat_name + + "(j int) " + TypeName(field) + " {\n"; + code += "\treturn rcv." + base_name + "(j)\n"; + code += "}\n\n"; + } } // Begin the creator function signature. @@ -687,14 +797,19 @@ class GoGenerator : public BaseGenerator { const size_t offset, std::string* code_ptr) { std::string& code = *code_ptr; const std::string field_var = namer_.Variable(field); - code += "func " + namer_.Type(struct_def) + "Add" + namer_.Function(field); - code += "(builder *flatbuffers.Builder, "; - code += field_var + " "; + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + const bool needs_compat = NeedsCompat(base_name, compat_name); + std::string param_type; if (!IsScalar(field.value.type.base_type) && (!struct_def.fixed)) { - code += "flatbuffers.UOffsetT"; + param_type = "flatbuffers.UOffsetT"; } else { - code += GenTypeGet(field.value.type); + param_type = GenTypeGet(field.value.type); } + + code += "func " + namer_.Type(struct_def) + "Add" + base_name; + code += "(builder *flatbuffers.Builder, "; + code += field_var + " " + param_type; code += ") {\n\t"; code += "builder.Prepend"; code += GenMethod(field); @@ -717,14 +832,26 @@ class GoGenerator : public BaseGenerator { } code += ")\n"; code += "}\n"; + + if (needs_compat) { + code += "func " + namer_.Type(struct_def) + "Add" + compat_name + + "(builder *flatbuffers.Builder, " + field_var + " " + param_type + + ") {\n"; + code += "\t" + namer_.Type(struct_def) + "Add" + base_name + + "(builder, " + field_var + ")\n"; + code += "}\n"; + } } // Set the value of one of the members of a table's vector. void BuildVectorOfTable(const StructDef& struct_def, const FieldDef& field, std::string* code_ptr) { std::string& code = *code_ptr; + const std::string base_name = namer_.Function(field); + const std::string compat_name = CompatFieldUpper(field); + const bool needs_compat = NeedsCompat(base_name, compat_name); code += "func " + namer_.Type(struct_def) + "Start"; - code += namer_.Function(field); + code += base_name; code += "Vector(builder *flatbuffers.Builder, numElems int) "; code += "flatbuffers.UOffsetT {\n\treturn builder.StartVector("; auto vector_type = field.value.type.VectorType(); @@ -733,6 +860,15 @@ class GoGenerator : public BaseGenerator { code += NumToString(elem_size); code += ", numElems, " + NumToString(alignment); code += ")\n}\n"; + + if (needs_compat) { + code += "func " + namer_.Type(struct_def) + "Start" + compat_name + + "Vector(builder *flatbuffers.Builder, numElems int) " + "flatbuffers.UOffsetT {\n"; + code += "\treturn " + namer_.Type(struct_def) + "Start" + base_name + + "Vector(builder, numElems)\n"; + code += "}\n"; + } } // Get the offset of the end of a table. @@ -808,12 +944,22 @@ class GoGenerator : public BaseGenerator { std::string setter = "rcv._tab.Mutate" + namer_.Method(GenTypeBasic(field.value.type)); GenReceiver(struct_def, code_ptr); - code += " Mutate" + namer_.Function(field); + const std::string base_suffix = namer_.Function(field); + const std::string compat_suffix = CompatFieldUpper(field); + code += " Mutate" + base_suffix; code += "(n " + GenTypeGet(field.value.type) + ") bool {\n\treturn " + setter; code += "(rcv._tab.Pos+flatbuffers.UOffsetT("; code += NumToString(field.value.offset) + "), "; code += CastToBaseType(field.value.type, "n") + ")\n}\n\n"; + + if (NeedsCompat(base_suffix, compat_suffix)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") Mutate" + + compat_suffix + "(n " + GenTypeGet(field.value.type) + + ") bool {\n"; + code += "\treturn rcv.Mutate" + base_suffix + "(n)\n"; + code += "}\n\n"; + } } // Mutate the value of a table's scalar. @@ -823,11 +969,21 @@ class GoGenerator : public BaseGenerator { std::string setter = "rcv._tab.Mutate" + namer_.Method(GenTypeBasic(field.value.type)) + "Slot"; GenReceiver(struct_def, code_ptr); - code += " Mutate" + namer_.Function(field); + const std::string base_suffix = namer_.Function(field); + const std::string compat_suffix = CompatFieldUpper(field); + code += " Mutate" + base_suffix; code += "(n " + GenTypeGet(field.value.type) + ") bool {\n\treturn "; code += setter + "(" + NumToString(field.value.offset) + ", "; code += CastToBaseType(field.value.type, "n") + ")\n"; code += "}\n\n"; + + if (NeedsCompat(base_suffix, compat_suffix)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") Mutate" + + compat_suffix + "(n " + GenTypeGet(field.value.type) + + ") bool {\n"; + code += "\treturn rcv.Mutate" + base_suffix + "(n)\n"; + code += "}\n\n"; + } } // Mutate an element of a vector of scalars. @@ -839,7 +995,9 @@ class GoGenerator : public BaseGenerator { std::string setter = "rcv._tab.Mutate" + namer_.Method(GenTypeBasic(vectortype)); GenReceiver(struct_def, code_ptr); - code += " Mutate" + namer_.Function(field); + const std::string base_suffix = namer_.Function(field); + const std::string compat_suffix = CompatFieldUpper(field); + code += " Mutate" + base_suffix; code += "(j int, n " + TypeName(field) + ") bool "; code += OffsetPrefix(field); code += "\t\ta := rcv._tab.Vector(o)\n"; @@ -850,6 +1008,13 @@ class GoGenerator : public BaseGenerator { code += "\t}\n"; code += "\treturn false\n"; code += "}\n\n"; + + if (NeedsCompat(base_suffix, compat_suffix)) { + code += "func (rcv *" + namer_.Type(struct_def) + ") Mutate" + + compat_suffix + "(j int, n " + TypeName(field) + ") bool {\n"; + code += "\treturn rcv.Mutate" + base_suffix + "(j, n)\n"; + code += "}\n\n"; + } } // Generate a struct field setter, conditioned on its child type(s). @@ -1023,7 +1188,7 @@ class GoGenerator : public BaseGenerator { field.value.type.enum_def != nullptr && field.value.type.enum_def->is_union) continue; - code += "\t" + namer_.Field(field) + " "; + code += "\t" + NativeFieldName(field) + " "; if (field.IsScalarOptional()) { code += "*"; } @@ -1117,7 +1282,7 @@ class GoGenerator : public BaseGenerator { if (field.deprecated) continue; if (IsScalar(field.value.type.base_type)) continue; - const std::string field_field = namer_.Field(field); + const std::string field_field = NativeFieldName(field); const std::string field_var = namer_.Variable(field); const std::string offset = field_var + "Offset"; @@ -1190,7 +1355,7 @@ class GoGenerator : public BaseGenerator { it != struct_def.fields.vec.end(); ++it) { const FieldDef& field = **it; if (field.deprecated) continue; - const std::string field_field = namer_.Field(field); + const std::string field_field = NativeFieldName(field); const std::string field_fn = namer_.Function(field); const std::string offset = namer_.Variable(field) + "Offset"; @@ -1239,7 +1404,7 @@ class GoGenerator : public BaseGenerator { it != struct_def.fields.vec.end(); ++it) { const FieldDef& field = **it; if (field.deprecated) continue; - const std::string field_field = namer_.Field(field); + const std::string field_field = NativeFieldName(field); const std::string field_var = namer_.Variable(field); const std::string length = field_var + "Length"; if (IsScalar(field.value.type.base_type)) { @@ -1325,10 +1490,10 @@ class GoGenerator : public BaseGenerator { const FieldDef& field = **it; if (field.value.type.base_type == BASE_TYPE_STRUCT) { StructPackArgs(*field.value.type.struct_def, - (nameprefix + namer_.Field(field) + ".").c_str(), + (nameprefix + NativeFieldName(field) + ".").c_str(), code_ptr); } else { - code += std::string(", t.") + nameprefix + namer_.Field(field); + code += std::string(", t.") + nameprefix + NativeFieldName(field); } } } @@ -1343,10 +1508,10 @@ class GoGenerator : public BaseGenerator { it != struct_def.fields.vec.end(); ++it) { const FieldDef& field = **it; if (field.value.type.base_type == BASE_TYPE_STRUCT) { - code += "\tt." + namer_.Field(field) + " = rcv." + + code += "\tt." + NativeFieldName(field) + " = rcv." + namer_.Method(field) + "(nil).UnPack()\n"; } else { - code += "\tt." + namer_.Field(field) + " = rcv." + + code += "\tt." + NativeFieldName(field) + " = rcv." + namer_.Method(field) + "()\n"; } } @@ -1395,6 +1560,43 @@ class GoGenerator : public BaseGenerator { EnumStringer(enum_def, code_ptr); } + std::string CompatFieldUpper(const FieldDef& field) const { + return SnakeToCamel(field.name, true); + } + + bool NeedsCompat(const std::string& original, + const std::string& compat) const { + return original != compat; + } + + std::string NativeFieldName(const FieldDef& field) const { + return CompatFieldUpper(field); + } + + std::string SnakeToCamel(const std::string& value, bool upper_first) const { + std::string result; + result.reserve(value.size()); + bool capitalize = upper_first; + for (char ch : value) { + if (ch == '_') { + capitalize = true; + continue; + } + unsigned char uch = static_cast(ch); + if (capitalize) { + result.push_back(static_cast(std::toupper(uch))); + capitalize = false; + } else { + result.push_back(static_cast(std::tolower(uch))); + } + } + if (!upper_first && !result.empty()) { + result[0] = static_cast( + std::tolower(static_cast(result[0]))); + } + return result; + } + // Returns the function name that is able to read a value of the given type. std::string GenGetter(const Type& type) { switch (type.base_type) { diff --git a/src/idl_gen_python.cpp b/src/idl_gen_python.cpp index 4c48b21b17d..b4b3e3e7d5e 100644 --- a/src/idl_gen_python.cpp +++ b/src/idl_gen_python.cpp @@ -1924,10 +1924,18 @@ class PythonGenerator : public BaseGenerator { import_list->erase(struct_import); } + std::string SafeVariable(const StructDef& struct_def) const { + auto variable = namer_.Variable(struct_def); + if (variable == namer_.Type(struct_def)) { + variable = namer_.Variable("tmp_" + struct_def.name); + } + return variable; + } + void InitializeFromBuf(const StructDef& struct_def, std::string* code_ptr) const { auto& code = *code_ptr; - const auto struct_var = namer_.Variable(struct_def); + const auto struct_var = SafeVariable(struct_def); const auto struct_type = namer_.Type(struct_def); code += GenIndents(1) + "@classmethod"; @@ -1941,7 +1949,7 @@ class PythonGenerator : public BaseGenerator { void InitializeFromPackedBuf(const StructDef& struct_def, std::string* code_ptr) const { auto& code = *code_ptr; - const auto struct_var = namer_.Variable(struct_def); + const auto struct_var = SafeVariable(struct_def); const auto struct_type = namer_.Type(struct_def); code += GenIndents(1) + "@classmethod"; @@ -1955,7 +1963,7 @@ class PythonGenerator : public BaseGenerator { void InitializeFromObjForObject(const StructDef& struct_def, std::string* code_ptr) const { auto& code = *code_ptr; - const auto struct_var = namer_.Variable(struct_def); + const auto struct_var = SafeVariable(struct_def); const auto struct_object = namer_.ObjectType(struct_def); code += GenIndents(1) + "@classmethod"; @@ -2025,14 +2033,29 @@ class PythonGenerator : public BaseGenerator { const auto field_field = namer_.Field(field); const auto field_method = namer_.Method(field); const auto struct_var = namer_.Variable(struct_def); + + // Find the associated type field + std::string type_field_name = field.name + "_type"; + const FieldDef* type_field = nullptr; + for (auto f : struct_def.fields.vec) { + if (f->name == type_field_name) { + type_field = f; + break; + } + } + FLATBUFFERS_ASSERT(type_field != nullptr); // Safety check + + const auto field_type_field = namer_.Field(*type_field); + const EnumDef& enum_def = *field.value.type.enum_def; auto union_type = namer_.Type(enum_def); if (parser_.opts.include_dependence_headers) { union_type = namer_.NamespacedType(enum_def) + "." + union_type; } + code += GenIndents(2) + "self." + field_field + " = " + union_type + - "Creator(" + "self." + field_field + "Type, " + struct_var + "." + + "Creator(" + "self." + field_type_field + ", " + struct_var + "." + field_method + "())"; } diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 42df4cde71e..e138fbdfe96 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -24,6 +24,7 @@ #include "flatbuffers/base.h" #include "flatbuffers/buffer.h" #include "flatbuffers/idl.h" +#include "flatbuffers/idlnames.h" #include "flatbuffers/reflection_generated.h" #include "flatbuffers/util.h" @@ -899,6 +900,7 @@ CheckedError Parser::AddField(StructDef& struct_def, const std::string& name, FieldIndexToOffset(static_cast(struct_def.fields.vec.size())); field.name = name; field.file = struct_def.file; + RecordIdlName(&field.name); field.value.type = type; if (struct_def.fixed) { // statically compute the field offset auto size = InlineSize(type); @@ -2432,6 +2434,9 @@ struct EnumValBuilder { auto first = enum_def.vals.vec.empty(); user_value = first; temp = new EnumVal(ev_name, first ? 0 : enum_def.vals.vec.back()->value); + + RecordIdlName(&temp->name); + return temp; } @@ -2439,6 +2444,9 @@ struct EnumValBuilder { FLATBUFFERS_ASSERT(!temp); user_value = true; temp = new EnumVal(ev_name, val); + + RecordIdlName(&temp->name); + return temp; } @@ -2688,6 +2696,7 @@ CheckedError Parser::StartStruct(const std::string& name, StructDef** dest) { struct_def.predecl = false; struct_def.name = name; struct_def.file = file_being_parsed_; + RecordIdlName(&struct_def.name); // Move this struct to the back of the vector just in case it was predeclared, // to preserve declaration order. *std::remove(structs_.vec.begin(), structs_.vec.end(), &struct_def) = @@ -3052,6 +3061,7 @@ CheckedError Parser::StartEnum(const std::string& name, bool is_union, EnumDef** dest) { auto& enum_def = *new EnumDef(); enum_def.name = name; + RecordIdlName(&enum_def.name); enum_def.file = file_being_parsed_; enum_def.doc_comment = doc_comment_; enum_def.is_union = is_union; @@ -4236,8 +4246,14 @@ bool EnumDef::Deserialize(Parser& parser, const reflection::Enum* _enum) { name = parser.UnqualifiedName(_enum->name()->str()); for (uoffset_t i = 0; i < _enum->values()->size(); ++i) { auto val = new EnumVal(); - if (!val->Deserialize(parser, _enum->values()->Get(i)) || - vals.Add(val->name, val)) { + if (!val->Deserialize(parser, _enum->values()->Get(i))) { + delete val; + return false; + } + + RecordIdlName(&val->name); + + if (vals.Add(val->name, val)) { delete val; return false; } diff --git a/src/options.cpp b/src/options.cpp new file mode 100644 index 00000000000..0ba30b59534 --- /dev/null +++ b/src/options.cpp @@ -0,0 +1,6 @@ +#include "flatbuffers/options.h" + +namespace flatbuffers { + +FlatCOptions global_options; +} diff --git a/src/util.cpp b/src/util.cpp index de6b68c65bc..0851946dae2 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -53,6 +53,8 @@ #include #include "flatbuffers/base.h" +#include "flatbuffers/idlnames.h" +#include "flatbuffers/options.h" namespace flatbuffers { @@ -446,6 +448,13 @@ bool ReadEnvironmentVariable(const char* var_name, std::string* _value) { std::string ConvertCase(const std::string& input, Case output_case, Case input_case) { + // If preserve-case is on, or the string is from the IDL names registry, + // return as-is. + + if (global_options.preserve_case && IsIdlName(input)) { + return input; + } + if (output_case == Case::kKeep) return input; // The output cases expect snake_case inputs, so if we don't have that input // format, try to convert to snake_case. diff --git a/tests/.gitignore b/tests/.gitignore index 25d3361959e..2d2d2d61e9e 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1,3 @@ # Generated files shouldn't be checked in for tests. -**_generated.h \ No newline at end of file +**_generated.h +preserve_case_cpp/ diff --git a/tests/CppPreserveCaseTest.cpp b/tests/CppPreserveCaseTest.cpp new file mode 100644 index 00000000000..9c8b943570a --- /dev/null +++ b/tests/CppPreserveCaseTest.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +#include "flatbuffers/flatbuffers.h" +#include "monster_test_generated.h" + +int main() { + flatbuffers::FlatBufferBuilder builder; + + auto stat_id = builder.CreateString("Sword"); + auto stat = MyGame::Example::CreateStat(builder, stat_id, 10, 0); + auto abilities = builder.CreateVector(std::vector{1, 2, 3, 4}); + auto string_vector = + builder.CreateVectorOfStrings(std::vector{"Alpha", "Beta"}); + auto longs_vector = builder.CreateVector(std::vector{11, 22, 33}); + auto simple_table = MyGame::Example::CreateTestSimpleTableWithEnum( + builder, MyGame::Example::Color_Red); + + auto monster_name = builder.CreateString("PreserveCase"); + MyGame::Example::MonsterBuilder monster_builder(builder); + monster_builder.add_mana(150); + monster_builder.add_hp(80); + monster_builder.add_name(monster_name); + monster_builder.add_testempty(stat); + monster_builder.add_testbool(true); + monster_builder.add_inventory(abilities); + monster_builder.add_testarrayofstring(string_vector); + monster_builder.add_vector_of_longs(longs_vector); + monster_builder.add_test_type(MyGame::Example::Any_TestSimpleTableWithEnum); + monster_builder.add_test(simple_table.Union()); + auto monster = monster_builder.Finish(); + builder.Finish(monster, MyGame::Example::MonsterIdentifier()); + + auto buffer = builder.GetBufferPointer(); + flatbuffers::Verifier verifier(buffer, builder.GetSize()); + if (!MyGame::Example::VerifyMonsterBuffer(verifier)) { + return 1; + } + + auto root = MyGame::Example::GetMonster(buffer); + if (root == nullptr) return 2; + if (root->mana() != 150) return 3; + if (root->hp() != 80) return 4; + if (root->name() == nullptr) return 5; + if (std::string(root->name()->c_str()) != "PreserveCase") return 6; + if (!MyGame::Example::MonsterBufferHasIdentifier(buffer)) return 7; + auto collected_stat = root->testempty(); + if (collected_stat == nullptr) return 8; + if (std::string(collected_stat->id()->c_str()) != "Sword") return 9; + if (!root->testbool()) return 10; + auto inventory = root->inventory(); + if (inventory == nullptr || inventory->size() != 4) return 11; + if (inventory->Get(0) != 1 || inventory->Get(3) != 4) return 12; + auto strings = root->testarrayofstring(); + if (strings == nullptr || strings->size() != 2) return 13; + if (strings->Get(0)->str() != "Alpha") return 14; + auto longs = root->vector_of_longs(); + if (longs == nullptr || longs->size() != 3) return 15; + if (longs->Get(2) != 33) return 16; + if (root->test_type() != MyGame::Example::Any_TestSimpleTableWithEnum) + return 17; + auto union_table = root->test_as_TestSimpleTableWithEnum(); + if (union_table == nullptr || + union_table->color() != MyGame::Example::Color_Red) + return 18; + + std::unique_ptr unpacked(root->UnPack()); + if (unpacked->name != "PreserveCase") return 19; + if (unpacked->mana != 150) return 20; + if (unpacked->testempty == nullptr) return 21; + if (unpacked->testempty->id != "Sword") return 22; + if (!unpacked->testbool) return 23; + if (unpacked->inventory.size() != 4 || unpacked->inventory[1] != 2) return 24; + if (unpacked->testarrayofstring.size() != 2 || + unpacked->testarrayofstring[1] != "Beta") + return 25; + if (unpacked->vector_of_longs.size() != 3 || + unpacked->vector_of_longs[0] != 11) + return 26; + if (unpacked->test.type != MyGame::Example::Any_TestSimpleTableWithEnum) + return 27; + if (unpacked->test.AsTestSimpleTableWithEnum()->color != + MyGame::Example::Color_Red) + return 28; + + flatbuffers::FlatBufferBuilder roundtrip; + roundtrip.Finish(MyGame::Example::Monster::Pack(roundtrip, unpacked.get()), + MyGame::Example::MonsterIdentifier()); + if (!MyGame::Example::MonsterBufferHasIdentifier( + roundtrip.GetBufferPointer())) + return 29; + + return 0; +} diff --git a/tests/CppTest.sh b/tests/CppTest.sh new file mode 100755 index 00000000000..47240ea1400 --- /dev/null +++ b/tests/CppTest.sh @@ -0,0 +1,56 @@ +#!/bin/bash -eu +# +# Runs the C++ FlatBuffers tests with both default naming and the +# --preserve-case option. + +pushd "$(dirname "$0")" >/dev/null +test_dir="$(pwd)" +repo_dir="$(cd .. && pwd)" + +echo "Running C++ tests (default naming)" +if [[ ! -x "${repo_dir}/flattests" ]]; then + echo "error: ${repo_dir}/flattests not found. Build the project first (e.g. cmake --build ./build --target flattests)." >&2 + exit 1 +fi +"${repo_dir}/flattests" + +echo "Running C++ tests (preserve-case)" +generated_dir="${test_dir}/preserve_case_cpp" +rm -rf "${generated_dir}" +mkdir -p "${generated_dir}" + +flatc_bin="${repo_dir}/flatc" +if [[ ! -x "${flatc_bin}" ]]; then + echo "error: ${flatc_bin} not found. Build flatc first." >&2 + exit 1 +fi + +base_opts=( + --cpp + --gen-mutable + --gen-object-api + --reflect-names + --gen-compare + --preserve-case + -o "${generated_dir}" + -I "${test_dir}/include_test" +) + +"${flatc_bin}" "${base_opts[@]}" \ + "${test_dir}/include_test/include_test1.fbs" \ + "${test_dir}/include_test/sub/include_test2.fbs" \ + "${test_dir}/monster_test.fbs" + +cxx=${CXX:-c++} +cxxflags=${CXXFLAGS:-} + +"${cxx}" ${cxxflags} -std=c++17 -Wall -Wextra \ + -I "${repo_dir}/include" \ + -I "${test_dir}" \ + -I "${generated_dir}" \ + "${test_dir}/CppPreserveCaseTest.cpp" \ + -o "${generated_dir}/CppPreserveCaseTest" + +"${generated_dir}/CppPreserveCaseTest" + +popd >/dev/null diff --git a/tests/DartTest.sh b/tests/DartTest.sh index 6907da823c2..90b058c1317 100755 --- a/tests/DartTest.sh +++ b/tests/DartTest.sh @@ -1,41 +1,45 @@ -#!/bin/sh -set -euo pipefail -# -# Copyright 2016 Google Inc. All rights reserved. -# -# 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. - -pushd "$(dirname $0)" >/dev/null +#!/bin/bash +set -eu + +script_dir="$(cd "$(dirname "$0")" && pwd)" +repo_dir="$(cd "${script_dir}/.." && pwd)" +dart_dir="${repo_dir}/dart" +test_dir="${dart_dir}/test" +preserve_dir="${dart_dir}/test_preserve_case" command -v dart >/dev/null 2>&1 || { - echo >&2 "Dart tests require dart to be in path but it's not installed. Aborting." - exit 1 + echo >&2 "Dart tests require dart to be in path but it's not installed. Aborting." + exit 1 +} + +pushd "${script_dir}" >/dev/null + +generate_preserve_case() { + local flatc="${repo_dir}/flatc" + local opts=(--dart --gen-object-api --preserve-case) + "${flatc}" "${opts[@]}" -I "${script_dir}/include_test" -o "${preserve_dir}" "${script_dir}/monster_test.fbs" + "${flatc}" "${opts[@]}" -I "${script_dir}/include_test/sub" -o "${preserve_dir}" "${script_dir}/include_test/include_test1.fbs" + "${flatc}" "${opts[@]}" -I "${script_dir}/include_test" -o "${preserve_dir}" "${script_dir}/include_test/sub/include_test2.fbs" + "${flatc}" "${opts[@]}" -o "${preserve_dir}" "${test_dir}/enums.fbs" + "${flatc}" "${opts[@]}" -o "${preserve_dir}" "${test_dir}/bool_structs.fbs" } -# output required files to the dart folder so that pub will be able to -# distribute them and more people can more easily run the dart tests -../flatc --dart --gen-object-api -I include_test -o ../dart/test monster_test.fbs -../flatc --dart --gen-object-api -I include_test/sub -o ../dart/test include_test/include_test1.fbs -../flatc --dart --gen-object-api -I include_test -o ../dart/test include_test/sub/include_test2.fbs -cp monsterdata_test.mon ../dart/test -cp monster_test.fbs ../dart/test +run_default_tests() { + echo "Running Dart tests (default naming)" + ( cd "${dart_dir}" && dart pub get && dart test "test/" ) +} -cd ../dart +run_preserve_case_tests() { + echo "Running Dart tests (preserve-case naming)" + if [[ ! -d "${preserve_dir}" ]]; then + echo >&2 "Missing ${preserve_dir}; create preserve-case test copies first." + exit 1 + fi + generate_preserve_case + ( cd "${dart_dir}" && dart test "test_preserve_case/" ) +} -../flatc --dart --gen-object-api -o ./test ./test/enums.fbs -../flatc --dart --gen-object-api -o ./test ./test/bool_structs.fbs +run_default_tests +run_preserve_case_tests -# update packages -dart pub get -# Execute the sample. -dart test +popd >/dev/null diff --git a/tests/GoTest.sh b/tests/GoTest.sh index b55cad5770c..8a632125a63 100755 --- a/tests/GoTest.sh +++ b/tests/GoTest.sh @@ -16,53 +16,74 @@ pushd "$(dirname $0)" >/dev/null test_dir="$(pwd)" -go_path=${test_dir}/go_gen -go_src=${go_path}/src - -# Emit Go code for the example schemas in the test dir: -../flatc -g --gen-object-api -I include_test -o ${go_src} monster_test.fbs optional_scalars.fbs -../flatc -g --gen-object-api -I include_test/sub -o ${go_src} include_test/order.fbs -../flatc -g --gen-object-api -o ${go_src}/Pizza include_test/sub/no_namespace.fbs - -# Go requires a particular layout of files in order to link multiple packages. -# Copy flatbuffer Go files to their own package directories to compile the -# test binary: -mkdir -p ${go_src}/github.com/google/flatbuffers/go -mkdir -p ${go_src}/flatbuffers_test - -cp -a ../go/* ./go_gen/src/github.com/google/flatbuffers/go -cp -a ./go_test.go ./go_gen/src/flatbuffers_test/ - -# https://stackoverflow.com/a/63545857/7024978 -# We need to turn off go modules for this script -# to work. -go env -w GO111MODULE=off - -# Run tests with necessary flags. -# Developers may wish to see more detail by appending the verbosity flag -# -test.v to arguments for this command, as in: -# go -test -test.v ... -# Developers may also wish to run benchmarks, which may be achieved with the -# flag -test.bench and the wildcard regexp ".": -# go -test -test.bench=. ... -GOPATH=${go_path} go test flatbuffers_test \ - --coverpkg=github.com/google/flatbuffers/go \ - --cpp_data=${test_dir}/monsterdata_test.mon \ - --out_data=${test_dir}/monsterdata_go_wire.mon \ - --bench=. \ - --benchtime=3s \ - --fuzz=true \ - --fuzz_fields=4 \ - --fuzz_objects=10000 - -GO_TEST_RESULT=$? -rm -rf ${go_path}/{pkg,src} -if [[ $GO_TEST_RESULT == 0 ]]; then - echo "OK: Go tests passed." -else - echo "KO: Go tests failed." +go_path_base=${test_dir}/go_gen + +function generate_go_code() { + local preserve_case_flag=$1 + local go_src=$2 + local preserve_case_opt="" + + if [ "${preserve_case_flag}" = "true" ]; then + preserve_case_opt="--preserve-case" + fi + + ../flatc -g --gen-object-api ${preserve_case_opt} -I include_test -o ${go_src} monster_test.fbs optional_scalars.fbs + ../flatc -g --gen-object-api ${preserve_case_opt} -I include_test/sub -o ${go_src} include_test/order.fbs + ../flatc -g --gen-object-api ${preserve_case_opt} -o ${go_src}/Pizza include_test/sub/no_namespace.fbs +} + +function run_go_suite() { + local variant=$1 + local preserve_case_flag=$2 + local test_source=$3 + + local go_path_variant=${go_path_base}_${variant} + local go_src=${go_path_variant}/src + local out_data=${test_dir}/monsterdata_go_wire_${variant}.mon + + echo "Running Go tests (${variant})" + + rm -rf "${go_path_variant}" + mkdir -p "${go_src}" + + generate_go_code "${preserve_case_flag}" "${go_src}" + + mkdir -p ${go_src}/github.com/google/flatbuffers/go + mkdir -p ${go_src}/flatbuffers_test + + cp -a ../go/* ${go_src}/github.com/google/flatbuffers/go + cp -a "${test_source}" ${go_src}/flatbuffers_test/go_test.go + + # https://stackoverflow.com/a/63545857/7024978 + # We need to turn off go modules for this script to work. + go env -w GO111MODULE=off + + GOPATH=${go_path_variant} go test flatbuffers_test \ + --coverpkg=github.com/google/flatbuffers/go \ + --cpp_data=${test_dir}/monsterdata_test.mon \ + --out_data=${out_data} \ + --bench=. \ + --benchtime=3s \ + --fuzz=true \ + --fuzz_fields=4 \ + --fuzz_objects=10000 + + local go_test_result=$? + rm -rf ${go_path_variant}/{pkg,src} + + if [[ ${go_test_result} != 0 ]]; then + echo "KO: Go tests failed for ${variant}." + # Re-enable go modules before exiting + go env -w GO111MODULE=on exit 1 -fi + fi +} + +# Run both default and preserve-case variants. +run_go_suite "default" "false" "./go_test.go" +run_go_suite "preserve_case" "true" "./go_test_preserve_case.go" + +echo "OK: Go tests passed for default and preserve-case variants." NOT_FMT_FILES=$(gofmt -l .) if [[ ${NOT_FMT_FILES} != "" ]]; then @@ -74,4 +95,4 @@ if [[ ${NOT_FMT_FILES} != "" ]]; then fi # Re-enable go modules when done tests -go env -w GO111MODULE=on +go env -w GO111MODULE=on diff --git a/tests/JavaTest.sh b/tests/JavaTest.sh new file mode 100755 index 00000000000..6cec81ca582 --- /dev/null +++ b/tests/JavaTest.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -eu + +script_dir="$(cd "$(dirname "$0")" && pwd)" +repo_dir="$(cd "${script_dir}/.." && pwd)" +java_dir="${repo_dir}/java" +flatc="${repo_dir}/flatc" + +if ! command -v java >/dev/null 2>&1; then + echo "Skipping Java tests: java executable not found." >&2 + exit 0 +fi +if ! command -v mvn >/dev/null 2>&1; then + echo "Skipping Java tests: mvn executable not found." >&2 + exit 0 +fi +if [[ ! -x "${flatc}" ]]; then + echo "Skipping Java tests: flatc executable not found at ${flatc}." >&2 + exit 0 +fi + +java_test_src="${java_dir}/src/test/java" +classpath_file="${java_dir}/target/test-classpath.txt" + +compile_tests() { + (cd "${java_dir}" && mvn -q -DskipTests clean test-compile) +} + +build_test_classpath() { + (cd "${java_dir}" && mvn -q -DincludeScope=test -DskipTests dependency:build-classpath -Dmdep.outputFile="target/test-classpath.txt" >/dev/null) +} + +run_junit_test() { + local test_name="$1" + local cp="$(cat "${classpath_file}"):${java_dir}/target/test-classes:${java_dir}/target/classes" + (cd "${java_dir}" && java -cp "${cp}" org.junit.runner.JUnitCore "${test_name}") +} + +generate_preserve_case_code() { + local out_dir="$1" + local opts=("--java" "--reflect-names" "--gen-mutable" "--preserve-case" "--java-package-prefix" "preservecase") + mkdir -p "${out_dir}" + + "${flatc}" "${opts[@]}" \ + -I "${script_dir}/include_test" \ + -I "${script_dir}/include_test/sub" \ + -o "${out_dir}" \ + "${script_dir}/monster_test.fbs" + + "${flatc}" "${opts[@]}" \ + -o "${out_dir}" \ + "${script_dir}/optional_scalars.fbs" \ + "${script_dir}/dictionary_lookup.fbs" \ + "${script_dir}/union_vector/union_vector.fbs" \ + "${script_dir}/namespace_test/namespace_test1.fbs" \ + "${script_dir}/namespace_test/namespace_test2.fbs" \ + "${script_dir}/arrays_test.fbs" +} + +cleanup_preserve_case() { + if [[ -n "${preserve_case_dir:-}" && -d "${preserve_case_dir}" ]]; then + rm -rf "${preserve_case_dir}" + fi + if [[ -f "${java_test_src}/JavaPreserveCaseTest.java" ]]; then + rm -f "${java_test_src}/JavaPreserveCaseTest.java" + fi + if [[ -d "${java_test_src}/preservecase" ]]; then + rm -rf "${java_test_src}/preservecase" + fi + rm -f "${java_dir}/target/test-classpath.txt" +} + +trap cleanup_preserve_case EXIT + +echo "Running Java tests (default naming)" +compile_tests +build_test_classpath +run_junit_test "JavaTest" + +echo "Running Java tests (preserve-case naming)" +preserve_case_dir="$(mktemp -d "${script_dir}/java_preserve_case_XXXX")" + +generate_preserve_case_code "${preserve_case_dir}" + +if [[ -d "${preserve_case_dir}/preservecase" ]]; then + cp -R "${preserve_case_dir}/preservecase" "${java_test_src}/" +fi + +cp "${script_dir}/java_preserve_case/JavaPreserveCaseTest.java" "${java_test_src}/JavaPreserveCaseTest.java" + +compile_tests +build_test_classpath +run_junit_test "JavaPreserveCaseTest" + +echo "Java tests (default + preserve-case) passed" diff --git a/tests/JsonSchemaTest.sh b/tests/JsonSchemaTest.sh new file mode 100755 index 00000000000..97e9381de4a --- /dev/null +++ b/tests/JsonSchemaTest.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -eu + +script_dir="$(cd "$(dirname "$0")" && pwd)" +repo_dir="$(cd "${script_dir}/.." && pwd)" +flatc="${repo_dir}/flatc" + +if [[ ! -x "${flatc}" ]]; then + echo "Skipping JSON Schema tests: flatc executable not found at ${flatc}." >&2 + exit 0 +fi + +schemas=( + "monster_test.fbs" + "arrays_test.fbs" +) +include_flags=( + "-I" "include_test" + "-I" "include_test/sub" +) + +golden_files=( + "monster_test.schema.json" + "arrays_test.schema.json" +) + +compare_output() { + local out_dir="$1" + for golden in "${golden_files[@]}"; do + local generated="${out_dir}/${golden}" + if ! diff -u "${script_dir}/${golden}" "${generated}"; then + echo "JSON Schema mismatch for ${golden}" >&2 + exit 1 + fi + done +} + +run_case() { + local label="$1" + local out_dir="$2" + shift 2 + + echo "Generating JSON Schemas (${label})" + rm -rf "${out_dir}" + mkdir -p "${out_dir}" + ( cd "${script_dir}" && "${flatc}" "$@" "${include_flags[@]}" -o "${out_dir}" "${schemas[@]}" ) + compare_output "${out_dir}" +} + +tmp_default="$(mktemp -d)" +tmp_preserve="$(mktemp -d)" +cleanup() { + rm -rf "${tmp_default}" "${tmp_preserve}" +} +trap cleanup EXIT + +run_case "default naming" "${tmp_default}" --jsonschema +run_case "preserve-case naming" "${tmp_preserve}" --jsonschema --preserve-case + +echo "JSON Schema tests (default + preserve-case) passed" diff --git a/tests/PHPTest.sh b/tests/PHPTest.sh new file mode 100755 index 00000000000..51b6cfad4da --- /dev/null +++ b/tests/PHPTest.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -eu + +script_dir="$(cd "$(dirname "$0")" && pwd)" +repo_dir="$(cd "${script_dir}/.." && pwd)" +preserve_dir="${repo_dir}/php/test_preserve_case" +flatc="${repo_dir}/flatc" + +if ! command -v php >/dev/null 2>&1; then + echo "Skipping PHP tests: php binary not found." >&2 + exit 0 +fi +if [[ ! -x "${flatc}" ]]; then + echo "Skipping PHP tests: flatc executable not found at ${flatc}." >&2 + exit 0 +fi + +run_php() { + PHP_GENERATED_ROOT="$1" PHP_UNION_GENERATED_DIR="$2" php "$3" +} + +generate_preserve_case() { + rm -rf "${preserve_dir}" + mkdir -p "${preserve_dir}" + local schemas=( + "${script_dir}/monster_test.fbs" + "${script_dir}/nested_union_test.fbs" + "${script_dir}/service_test.fbs" + "${script_dir}/union_vector/union_vector.fbs" + "${script_dir}/include_test/order.fbs" + "${script_dir}/include_test/sub/no_namespace.fbs" + ) + "${flatc}" --php --preserve-case --gen-object-api \ + -I "${script_dir}/include_test" \ + -I "${script_dir}/include_test/sub" \ + -o "${preserve_dir}" \ + "${schemas[@]}" +} + +echo "Running PHP tests (default naming)" +run_php "${script_dir}" "${script_dir}/union_vector" "${script_dir}/phpTest.php" +run_php "${script_dir}" "${script_dir}/union_vector" "${script_dir}/phpUnionVectorTest.php" + +echo "Running PHP tests (preserve-case naming)" +generate_preserve_case +run_php "${preserve_dir}" "${preserve_dir}" "${script_dir}/phpTestPreserveCase.php" +run_php "${preserve_dir}" "${preserve_dir}" "${script_dir}/phpUnionVectorTestPreserveCase.php" + +echo "PHP tests (default + preserve-case) passed" diff --git a/tests/PythonTest.sh b/tests/PythonTest.sh index 90bee57244e..74512f8d1d5 100755 --- a/tests/PythonTest.sh +++ b/tests/PythonTest.sh @@ -21,13 +21,29 @@ test_dir="$(pwd)" gen_code_path=${test_dir} runtime_library_dir=${test_dir}/../python -# Emit Python code for the example schema in the test dir: -${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_test.fbs --gen-object-api -${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_test.fbs --gen-object-api --gen-onefile -${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_extra.fbs --gen-object-api --python-typing --gen-compare -${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api --python-typing -${test_dir}/../flatc -p -o ${gen_code_path} -I include_test nested_union_test.fbs --gen-object-api --python-typing --python-decode-obj-api-strings -${test_dir}/../flatc -p -o ${gen_code_path} -I include_test service_test.fbs --grpc --grpc-python-typed-handlers --python-typing --no-python-gen-numpy --gen-onefile +# Clean up any previous generated files +rm -rf ${gen_code_path}/MyGame +mkdir -p ${gen_code_path} + +# Function to generate code (with or without preserve-case) +function generate_code() { + preserve_case_flag=$1 + + echo "Generating code (preserve-case: ${preserve_case_flag})..." + + if [ "${preserve_case_flag}" = "true" ]; then + preserve_case_opt="--preserve-case" + else + preserve_case_opt="" + fi + + ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_test.fbs --gen-object-api ${preserve_case_opt} + ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_test.fbs --gen-object-api --gen-onefile ${preserve_case_opt} + ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_extra.fbs --gen-object-api --python-typing --gen-compare ${preserve_case_opt} + ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api --python-typing ${preserve_case_opt} + ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test nested_union_test.fbs --gen-object-api --python-typing --python-decode-obj-api-strings ${preserve_case_opt} + ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test service_test.fbs --grpc --grpc-python-typed-handlers --python-typing --no-python-gen-numpy --gen-onefile ${preserve_case_opt} +} # Syntax: run_tests # @@ -35,6 +51,9 @@ interpreters_tested=() function run_tests() { if $(which ${1} >/dev/null); then echo "Testing with interpreter: ${1}" + + # First run without preserve-case + generate_code false PYTHONDONTWRITEBYTECODE=1 \ JYTHONDONTWRITEBYTECODE=1 \ PYTHONPATH=${runtime_library_dir}:${gen_code_path} \ @@ -42,7 +61,16 @@ function run_tests() { COMPARE_GENERATED_TO_GO=0 \ COMPARE_GENERATED_TO_JAVA=0 \ $1 py_test.py $2 $3 $4 $5 $6 + + # Then run with preserve-case + generate_code true + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONPATH=${runtime_library_dir}:${gen_code_path} \ + $1 py_test_preserve_case.py 0 0 0 0 false + + # Flexbuffers test for python3 only if [ $1 = python3 ]; then + generate_code false PYTHONDONTWRITEBYTECODE=1 \ PYTHONPATH=${runtime_library_dir}:${gen_code_path} \ $1 py_flexbuffers_test.py @@ -60,29 +88,23 @@ run_tests python3 100 100 100 100 false run_tests python3 100 100 100 100 true run_tests pypy 100 100 100 100 false -# NOTE: We'd like to support python2.5 in the future. - -# NOTE: Jython 2.7.0 fails due to a bug in the stdlib `struct` library: -# http://bugs.jython.org/issue2188 - if [ ${#interpreters_tested[@]} -eq 0 ]; then echo "No Python interpeters found on this system, could not run tests." exit 1 fi -# Run test suite with default python intereter. -# (If the Python program `coverage` is available, it will be run, too. -# Install `coverage` with `pip install coverage`.) +# Run test suite with default python interpreter. if $(which coverage >/dev/null); then echo 'Found coverage utility, running coverage with default Python:' + generate_code false PYTHONDONTWRITEBYTECODE=1 \ PYTHONPATH=${runtime_library_dir}:${gen_code_path} \ coverage run --source=flatbuffers,MyGame py_test.py 0 0 0 0 false > /dev/null echo - cov_result=`coverage report --omit="*flatbuffers/vendor*,*py_test*" \ - | tail -n 1 | awk ' { print $4 } '` + cov_result=`coverage report --omit="*flatbuffers/vendor*,*py_test*"` \ + `| tail -n 1 | awk ' { print $4 } '` echo "Code coverage: ${cov_result}" else echo -n "Did not find coverage utility for default Python, skipping. " @@ -90,4 +112,4 @@ else fi echo -echo "OK: all tests passed for ${#interpreters_tested[@]} interpreters: ${interpreters_tested[@]}." +echo "OK: all tests passed for ${#interpreters_tested[@]} interpreters: ${interpreters_tested[@]}." \ No newline at end of file diff --git a/tests/RustTest.sh b/tests/RustTest.sh index 1e7d0eed9d4..21e9a0618a0 100755 --- a/tests/RustTest.sh +++ b/tests/RustTest.sh @@ -21,6 +21,60 @@ if [[ "$1" == "mips-unknown-linux-gnu" ]]; then export CARGO_TARGET_MIPS_UNKNOWN_LINUX_GNU_RUNNER="qemu-mips -L /usr/mips-linux-gnu" fi +export PATH="${HOME}/.cargo/bin:${PATH}" +CARGO_BIN="${HOME}/.cargo/bin/cargo" +if [[ ! -x "${CARGO_BIN}" ]]; then + CARGO_BIN="cargo" +fi + +function generate_preserve_case_rust() { + local out_root="preserve_case_rust" + local base_opts=( + --rust + --reflect-names + --gen-mutable + --gen-object-api + --gen-all + --gen-name-strings + --rust-module-root-file + --preserve-case + ) + + rm -rf "${out_root}/monster_test" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/monster_test" -I include_test monster_test.fbs + + rm -rf "${out_root}/include_test1" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/include_test1" -I include_test include_test/include_test1.fbs + + rm -rf "${out_root}/include_test2" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/include_test2" -I include_test include_test/sub/include_test2.fbs + + rm -rf "${out_root}/optional_scalars" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/optional_scalars" optional_scalars.fbs + + rm -rf "${out_root}/arrays_test" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/arrays_test" arrays_test.fbs + + rm -rf "${out_root}/namespace_test" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/namespace_test" namespace_test/namespace_test1.fbs namespace_test/namespace_test2.fbs + + rm -rf "${out_root}/keyword_test" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/keyword_test" keyword_test.fbs + + rm -rf "${out_root}/rust_namer_test" + ../flatc "${base_opts[@]}" \ + -o "${out_root}/rust_namer_test" rust_namer_test.fbs +} + +generate_preserve_case_rust + function check_test_result() { if [[ $? == 0 ]]; then @@ -32,39 +86,39 @@ function check_test_result() { } cd ./rust_serialize_test -cargo run $TARGET_FLAG -- --quiet +"${CARGO_BIN}" run $TARGET_FLAG -- --quiet check_test_result "Rust serde tests" cd ../rust_no_std_compilation_test rustup install nightly rustup component add rust-src --toolchain nightly rustup target add thumbv7m-none-eabi -cargo +nightly build +"${CARGO_BIN}" +nightly build check_test_result "Rust flatbuffers test no_std compilation" cd ../rust_usage_test -cargo test $TARGET_FLAG -- --quiet +"${CARGO_BIN}" test $TARGET_FLAG -- --quiet check_test_result "Rust tests" -cargo test $TARGET_FLAG --no-default-features -- --quiet +"${CARGO_BIN}" test $TARGET_FLAG --no-default-features -- --quiet check_test_result "Rust tests (no_std)" -cargo run $TARGET_FLAG --bin=flatbuffers_alloc_check +"${CARGO_BIN}" run $TARGET_FLAG --bin=flatbuffers_alloc_check check_test_result "Rust flatbuffers heap alloc test" -cargo run $TARGET_FLAG --bin=flexbuffers_alloc_check +"${CARGO_BIN}" run $TARGET_FLAG --bin=flexbuffers_alloc_check check_test_result "Rust flexbuffers heap alloc test" rustup component add clippy -cargo clippy $TARGET_FLAG +"${CARGO_BIN}" clippy $TARGET_FLAG check_test_result "No Cargo clippy lints test" -cargo bench $TARGET_FLAG +"${CARGO_BIN}" bench $TARGET_FLAG # This test is dependent on flatc. if [[ -f ../../flatc ]]; then cd outdir - cargo test + "${CARGO_BIN}" test check_test_result "Rust generated file in \$OUT_DIR" cd .. fi @@ -72,5 +126,5 @@ fi # RUST_NIGHTLY environment variable set in dockerfile. if [[ $RUST_NIGHTLY == 1 ]]; then rustup +nightly component add miri - MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test + MIRIFLAGS="-Zmiri-disable-isolation" "${CARGO_BIN}" +nightly miri test fi diff --git a/tests/TestAll.sh b/tests/TestAll.sh index a7741b951a4..7f2553ad610 100755 --- a/tests/TestAll.sh +++ b/tests/TestAll.sh @@ -20,9 +20,7 @@ python3 ts/TypeScriptTest.py echo "************************ C++:" -cd .. -./flattests -cd tests +./CppTest.sh echo "************************ C#:" diff --git a/tests/TypeScriptTest.sh b/tests/TypeScriptTest.sh new file mode 100755 index 00000000000..1182d24fc61 --- /dev/null +++ b/tests/TypeScriptTest.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eu + +script_dir="$(cd "$(dirname "$0")" && pwd)" +repo_dir="$(cd "${script_dir}/.." && pwd)" +flatc="${repo_dir}/flatc" + +if ! command -v node >/dev/null 2>&1; then + echo "Skipping TypeScript tests: node executable not found." >&2 + exit 0 +fi + +if ! command -v npm >/dev/null 2>&1; then + echo "Skipping TypeScript tests: npm executable not found." >&2 + exit 0 +fi + +if [[ ! -x "${flatc}" ]]; then + echo "Skipping TypeScript tests: flatc executable not found at ${flatc}." >&2 + exit 0 +fi + +(cd "${script_dir}/ts" && python3 TypeScriptTest.py) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/go_test_preserve_case.go b/tests/go_test_preserve_case.go new file mode 100644 index 00000000000..5ecbef96566 --- /dev/null +++ b/tests/go_test_preserve_case.go @@ -0,0 +1,2575 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +package main + +import ( + mygame "MyGame" // refers to generated code + example "MyGame/Example" // refers to generated code + pizza "Pizza" + "encoding/json" + optional_scalars "optional_scalars" // refers to generated code + order "order" + + "bytes" + "flag" + "fmt" + "os" + "reflect" + "sort" + "testing" + "testing/quick" + + flatbuffers "github.com/google/flatbuffers/go" +) + +var ( + cppData, javaData, outData string + fuzz bool + fuzzFields, fuzzObjects int +) + +func init() { + flag.StringVar(&cppData, "cpp_data", "", + "location of monsterdata_test.mon to verify against (required)") + flag.StringVar(&javaData, "java_data", "", + "location of monsterdata_java_wire.mon to verify against (optional)") + flag.StringVar(&outData, "out_data", "", + "location to write generated Go data") + flag.BoolVar(&fuzz, "fuzz", false, "perform fuzzing") + flag.IntVar(&fuzzFields, "fuzz_fields", 4, "fields per fuzzer object") + flag.IntVar(&fuzzObjects, "fuzz_objects", 10000, + "number of fuzzer objects (higher is slower and more thorough") +} + +// Store specific byte patterns in these variables for the fuzzer. These +// values are taken verbatim from the C++ function FuzzTest1. +var ( + overflowingInt32Val = flatbuffers.GetInt32([]byte{0x83, 0x33, 0x33, 0x33}) + overflowingInt64Val = flatbuffers.GetInt64([]byte{0x84, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}) +) + +func TestMain(m *testing.M) { + flag.Parse() + if cppData == "" { + fmt.Fprintf(os.Stderr, "cpp_data argument is required\n") + os.Exit(1) + } + os.Exit(m.Run()) +} + +// TestTextParsing test if text parsing works with object API. +func TestTextParsing(t *testing.T) { + expectedMonster := example.MonsterT{ + Mana: 42, + Name: "foo", + LongEnumNormalDefault: example.LongEnumLongTwo, + } + + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(expectedMonster); err != nil { + t.Fatal(err) + } + + var monster example.MonsterT + if err := json.NewDecoder(buf).Decode(&monster); err != nil { + t.Fatal(err) + } + + if monster.Mana != expectedMonster.Mana { + t.Fatal("wrong mana:", monster.Mana) + } + if monster.Name != expectedMonster.Name { + t.Fatal("wrong name:", monster.Name) + } + if monster.LongEnumNormalDefault != expectedMonster.LongEnumNormalDefault { + t.Fatal("wrong enum:", monster.LongEnumNormalDefault) + } +} + +func CheckNoNamespaceImport(fail func(string, ...interface{})) { + const size = 13 + // Order a pizza with specific size + builder := flatbuffers.NewBuilder(0) + ordered_pizza := pizza.PizzaT{Size: size} + food := order.FoodT{Pizza: &ordered_pizza} + builder.Finish(food.Pack(builder)) + + // Receive order + received_food := order.GetRootAsFood(builder.FinishedBytes(), 0) + received_pizza := received_food.Pizza(nil).UnPack() + + // Check if received pizza is equal to ordered pizza + if !reflect.DeepEqual(ordered_pizza, *received_pizza) { + fail(FailString("no namespace import", ordered_pizza, received_pizza)) + } +} + +// TestAll runs all checks, failing if any errors occur. +func TestAll(t *testing.T) { + // Verify that the Go FlatBuffers runtime library generates the + // expected bytes (does not use any schema): + CheckByteLayout(t.Fatalf) + CheckMutateMethods(t.Fatalf) + + // Verify that panics are raised during exceptional conditions: + CheckNotInObjectError(t.Fatalf) + CheckStringIsNestedError(t.Fatalf) + CheckByteStringIsNestedError(t.Fatalf) + CheckStructIsNotInlineError(t.Fatalf) + CheckFinishedBytesError(t.Fatalf) + CheckSharedStrings(t.Fatalf) + CheckEmptiedBuilder(t.Fatalf) + + // Verify that GetRootAs works for non-root tables + CheckGetRootAsForNonRootTable(t.Fatalf) + CheckTableAccessors(t.Fatalf) + + // Verify that using the generated Go code builds a buffer without + // returning errors: + generated, off := CheckGeneratedBuild(false, false, t.Fatalf) + + // Verify that the buffer generated by Go code is readable by the + // generated Go code: + CheckReadBuffer(generated, off, false, t.Fatalf) + CheckMutateBuffer(generated, off, false, t.Fatalf) + CheckObjectAPI(generated, off, false, t.Fatalf) + + // Generate the buffer again, with file identifier. + generated, off = CheckGeneratedBuild(false, true, t.Fatalf) + + // Check that this buffer with file identifier is usable + // and that the file identifier is correct. + CheckReadBuffer(generated, off, false, t.Fatalf) + CheckMutateBuffer(generated, off, false, t.Fatalf) + CheckObjectAPI(generated, off, false, t.Fatalf) + CheckFileIdentifier(generated, off, false, t.Fatalf) + + // Verify that the buffer generated by C++ code is readable by the + // generated Go code: + monsterDataCpp, err := os.ReadFile(cppData) + if err != nil { + t.Fatal(err) + } + CheckReadBuffer(monsterDataCpp, 0, false, t.Fatalf) + CheckMutateBuffer(monsterDataCpp, 0, false, t.Fatalf) + CheckObjectAPI(monsterDataCpp, 0, false, t.Fatalf) + CheckFileIdentifier(monsterDataCpp, 0, false, t.Fatalf) + + // Verify that vtables are deduplicated when written: + CheckVtableDeduplication(t.Fatalf) + + // Verify the enum names + CheckEnumNames(t.Fatalf) + + // Verify enum String methods + CheckEnumString(t.Fatalf) + + // Verify the enum values maps + CheckEnumValues(t.Fatalf) + + // Verify that the Go code used in FlatBuffers documentation passes + // some sanity checks: + CheckDocExample(generated, off, t.Fatalf) + + // Check Builder.CreateByteVector + CheckCreateByteVector(t.Fatalf) + + // Check a parent namespace import + CheckParentNamespace(t.Fatalf) + + // Check a no namespace import + CheckNoNamespaceImport(t.Fatalf) + + // Check size-prefixed flatbuffers + CheckSizePrefixedBuffer(t.Fatalf) + + // Check that optional scalars works + CheckOptionalScalars(t.Fatalf) + + // Check that getting vector element by key works + CheckByKey(t.Fatalf) + + // If the filename of the FlatBuffers file generated by the Java test + // is given, check that Go code can read it, and that Go code + // generates an identical buffer when used to create the example data: + if javaData != "" { + monsterDataJava, err := os.ReadFile(javaData) + if err != nil { + t.Fatal(err) + } + CheckReadBuffer(monsterDataJava, 0, false, t.Fatalf) + CheckByteEquality(generated[off:], monsterDataJava, t.Fatalf) + } + + // Verify that various fuzzing scenarios produce a valid FlatBuffer. + if fuzz { + checkFuzz(fuzzFields, fuzzObjects, t.Fatalf) + } + + // Write the generated buffer out to a file: + err = os.WriteFile(outData, generated[off:], os.FileMode(0644)) + if err != nil { + t.Fatal(err) + } +} + +// CheckReadBuffer checks that the given buffer is evaluated correctly +// as the example Monster. +func CheckReadBuffer(buf []byte, offset flatbuffers.UOffsetT, sizePrefix bool, fail func(string, ...interface{})) { + // try the two ways of generating a monster + var monster1 *example.Monster + monster2 := &example.Monster{} + + if sizePrefix { + monster1 = example.GetSizePrefixedRootAsMonster(buf, offset) + flatbuffers.GetSizePrefixedRootAs(buf, offset, monster2) + } else { + monster1 = example.GetRootAsMonster(buf, offset) + flatbuffers.GetRootAs(buf, offset, monster2) + } + + for _, monster := range []*example.Monster{monster1, monster2} { + if got := monster.Hp(); 80 != got { + fail(FailString("hp", 80, got)) + } + + // default + if got := monster.Mana(); 150 != got { + fail(FailString("mana", 150, got)) + } + + if got := monster.Name(); !bytes.Equal([]byte("MyMonster"), got) { + fail(FailString("name", "MyMonster", got)) + } + + if got := monster.Color(); example.ColorBlue != got { + fail(FailString("color", example.ColorBlue, got)) + } + + if got := monster.Testbool(); true != got { + fail(FailString("testbool", true, got)) + } + + // initialize a Vec3 from Pos() + vec := new(example.Vec3) + vec = monster.Pos(vec) + if vec == nil { + fail("vec3 initialization failed") + } + + // check that new allocs equal given ones: + vec2 := monster.Pos(nil) + if !reflect.DeepEqual(vec, vec2) { + fail("fresh allocation failed") + } + + // verify the properties of the Vec3 + if got := vec.X(); float32(1.0) != got { + fail(FailString("Pos.X", float32(1.0), got)) + } + + if got := vec.Y(); float32(2.0) != got { + fail(FailString("Pos.Y", float32(2.0), got)) + } + + if got := vec.Z(); float32(3.0) != got { + fail(FailString("Pos.Z", float32(3.0), got)) + } + + if got := vec.Test1(); float64(3.0) != got { + fail(FailString("Pos.Test1", float64(3.0), got)) + } + + if got := vec.Test2(); example.ColorGreen != got { + fail(FailString("Pos.Test2", example.ColorGreen, got)) + } + + // initialize a Test from Test3(...) + t := new(example.Test) + t = vec.Test3(t) + if t == nil { + fail("vec.Test3(&t) failed") + } + + // check that new allocs equal given ones: + t2 := vec.Test3(nil) + if !reflect.DeepEqual(t, t2) { + fail("fresh allocation failed") + } + + // verify the properties of the Test + if got := t.A(); int16(5) != got { + fail(FailString("t.A()", int16(5), got)) + } + + if got := t.B(); int8(6) != got { + fail(FailString("t.B()", int8(6), got)) + } + + if got := monster.TestType(); example.AnyMonster != got { + fail(FailString("monster.TestType()", example.AnyMonster, got)) + } + + // initialize a Table from a union field Test(...) + var table2 flatbuffers.Table + if ok := monster.Test(&table2); !ok { + fail("monster.Test(&monster2) failed") + } + + // initialize a Monster from the Table from the union + var monster2 example.Monster + monster2.Init(table2.Bytes, table2.Pos) + + if got := monster2.Name(); !bytes.Equal([]byte("Fred"), got) { + fail(FailString("monster2.Name()", "Fred", got)) + } + + inventorySlice := monster.InventoryBytes() + if len(inventorySlice) != monster.InventoryLength() { + fail(FailString("len(monster.InventoryBytes) != monster.InventoryLength", len(inventorySlice), monster.InventoryLength())) + } + + if got := monster.InventoryLength(); 5 != got { + fail(FailString("monster.InventoryLength", 5, got)) + } + + invsum := 0 + l := monster.InventoryLength() + for i := 0; i < l; i++ { + v := monster.Inventory(i) + if v != inventorySlice[i] { + fail(FailString("monster inventory slice[i] != Inventory(i)", v, inventorySlice[i])) + } + invsum += int(v) + } + if invsum != 10 { + fail(FailString("monster inventory sum", 10, invsum)) + } + + if got := monster.Test4Length(); 2 != got { + fail(FailString("monster.Test4Length()", 2, got)) + } + + var test0 example.Test + ok := monster.Test4(&test0, 0) + if !ok { + fail(FailString("monster.Test4(&test0, 0)", true, ok)) + } + + var test1 example.Test + ok = monster.Test4(&test1, 1) + if !ok { + fail(FailString("monster.Test4(&test1, 1)", true, ok)) + } + + // the position of test0 and test1 are swapped in monsterdata_java_wire + // and monsterdata_test_wire, so ignore ordering + v0 := test0.A() + v1 := test0.B() + v2 := test1.A() + v3 := test1.B() + sum := int(v0) + int(v1) + int(v2) + int(v3) + + if 100 != sum { + fail(FailString("test0 and test1 sum", 100, sum)) + } + + if got := monster.TestarrayofstringLength(); 2 != got { + fail(FailString("Testarrayofstring length", 2, got)) + } + + if got := monster.Testarrayofstring(0); !bytes.Equal([]byte("test1"), got) { + fail(FailString("Testarrayofstring(0)", "test1", got)) + } + + if got := monster.Testarrayofstring(1); !bytes.Equal([]byte("test2"), got) { + fail(FailString("Testarrayofstring(1)", "test2", got)) + } + } +} + +// CheckFileIdentifier checks the "MONS" file identifier +func CheckFileIdentifier(buf []byte, offset flatbuffers.UOffsetT, sizePrefix bool, fail func(string, ...interface{})) { + // Strip offset + buf = buf[offset:] + + var fileIdentifier string + var hasFileIdentifier bool + + if sizePrefix { + fileIdentifier = flatbuffers.GetSizePrefixedBufferIdentifier(buf) + hasFileIdentifier = example.SizePrefixedMonsterBufferHasIdentifier(buf) + } else { + fileIdentifier = flatbuffers.GetBufferIdentifier(buf) + hasFileIdentifier = example.MonsterBufferHasIdentifier(buf) + } + + expectedFileIdentifier := "MONS" + if fileIdentifier != expectedFileIdentifier { + fail("expected file identifier %q, got %q", expectedFileIdentifier, fileIdentifier) + } + if !hasFileIdentifier { + fail("did not find file identifier") + } +} + +// CheckMutateBuffer checks that the given buffer can be mutated correctly +// as the example Monster. Only available scalar values are mutated. +func CheckMutateBuffer(org []byte, offset flatbuffers.UOffsetT, sizePrefix bool, fail func(string, ...interface{})) { + // make a copy to mutate + buf := make([]byte, len(org)) + copy(buf, org) + + // load monster data from the buffer + var monster *example.Monster + if sizePrefix { + monster = example.GetSizePrefixedRootAsMonster(buf, offset) + } else { + monster = example.GetRootAsMonster(buf, offset) + } + + // test case struct + type testcase struct { + field string + testfn func() bool + } + + testForOriginalValues := []testcase{ + testcase{"Hp", func() bool { return monster.Hp() == 80 }}, + testcase{"Mana", func() bool { return monster.Mana() == 150 }}, + testcase{"Testbool", func() bool { return monster.Testbool() == true }}, + testcase{"Pos.X'", func() bool { return monster.Pos(nil).X() == float32(1.0) }}, + testcase{"Pos.Y'", func() bool { return monster.Pos(nil).Y() == float32(2.0) }}, + testcase{"Pos.Z'", func() bool { return monster.Pos(nil).Z() == float32(3.0) }}, + testcase{"Pos.Test1'", func() bool { return monster.Pos(nil).Test1() == float64(3.0) }}, + testcase{"Pos.Test2'", func() bool { return monster.Pos(nil).Test2() == example.ColorGreen }}, + testcase{"Pos.Test3.A", func() bool { return monster.Pos(nil).Test3(nil).A() == int16(5) }}, + testcase{"Pos.Test3.B", func() bool { return monster.Pos(nil).Test3(nil).B() == int8(6) }}, + testcase{"Inventory[2]", func() bool { return monster.Inventory(2) == byte(2) }}, + } + + testMutability := []testcase{ + testcase{"Hp", func() bool { return monster.MutateHp(70) }}, + testcase{"Mana", func() bool { return !monster.MutateMana(140) }}, + testcase{"Testbool", func() bool { return monster.MutateTestbool(false) }}, + testcase{"Pos.X", func() bool { return monster.Pos(nil).MutateX(10.0) }}, + testcase{"Pos.Y", func() bool { return monster.Pos(nil).MutateY(20.0) }}, + testcase{"Pos.Z", func() bool { return monster.Pos(nil).MutateZ(30.0) }}, + testcase{"Pos.Test1", func() bool { return monster.Pos(nil).MutateTest1(30.0) }}, + testcase{"Pos.Test2", func() bool { return monster.Pos(nil).MutateTest2(example.ColorBlue) }}, + testcase{"Pos.Test3.A", func() bool { return monster.Pos(nil).Test3(nil).MutateA(50) }}, + testcase{"Pos.Test3.B", func() bool { return monster.Pos(nil).Test3(nil).MutateB(60) }}, + testcase{"Inventory[2]", func() bool { return monster.MutateInventory(2, 200) }}, + } + + testForMutatedValues := []testcase{ + testcase{"Hp", func() bool { return monster.Hp() == 70 }}, + testcase{"Mana", func() bool { return monster.Mana() == 150 }}, + testcase{"Testbool", func() bool { return monster.Testbool() == false }}, + testcase{"Pos.X'", func() bool { return monster.Pos(nil).X() == float32(10.0) }}, + testcase{"Pos.Y'", func() bool { return monster.Pos(nil).Y() == float32(20.0) }}, + testcase{"Pos.Z'", func() bool { return monster.Pos(nil).Z() == float32(30.0) }}, + testcase{"Pos.Test1'", func() bool { return monster.Pos(nil).Test1() == float64(30.0) }}, + testcase{"Pos.Test2'", func() bool { return monster.Pos(nil).Test2() == example.ColorBlue }}, + testcase{"Pos.Test3.A", func() bool { return monster.Pos(nil).Test3(nil).A() == int16(50) }}, + testcase{"Pos.Test3.B", func() bool { return monster.Pos(nil).Test3(nil).B() == int8(60) }}, + testcase{"Inventory[2]", func() bool { return monster.Inventory(2) == byte(200) }}, + } + + testInvalidEnumValues := []testcase{ + testcase{"Pos.Test2", func() bool { return monster.Pos(nil).MutateTest2(example.Color(20)) }}, + testcase{"Pos.Test2", func() bool { return monster.Pos(nil).Test2() == example.Color(20) }}, + } + + // make sure original values are okay + for _, t := range testForOriginalValues { + if !t.testfn() { + fail("field '" + t.field + "' doesn't have the expected original value") + } + } + + // try to mutate fields and check mutability + for _, t := range testMutability { + if !t.testfn() { + fail(FailString("field '"+t.field+"' failed mutability test", true, false)) + } + } + + // test whether values have changed + for _, t := range testForMutatedValues { + if !t.testfn() { + fail("field '" + t.field + "' doesn't have the expected mutated value") + } + } + + // make sure the buffer has changed + if reflect.DeepEqual(buf, org) { + fail("mutate buffer failed") + } + + // To make sure the buffer has changed accordingly + // Read data from the buffer and verify all fields + if sizePrefix { + monster = example.GetSizePrefixedRootAsMonster(buf, offset) + } else { + monster = example.GetRootAsMonster(buf, offset) + } + + for _, t := range testForMutatedValues { + if !t.testfn() { + fail("field '" + t.field + "' doesn't have the expected mutated value") + } + } + + // a couple extra tests for "invalid" enum values, which don't correspond to + // anything in the schema, but are allowed + for _, t := range testInvalidEnumValues { + if !t.testfn() { + fail("field '" + t.field + "' doesn't work with an invalid enum value") + } + } + + // reverting all fields to original values should + // re-create the original buffer. Mutate all fields + // back to their original values and compare buffers. + // This test is done to make sure mutations do not do + // any unnecessary changes to the buffer. + if sizePrefix { + monster = example.GetSizePrefixedRootAsMonster(buf, offset) + } else { + monster = example.GetRootAsMonster(buf, offset) + } + + monster.MutateHp(80) + monster.MutateTestbool(true) + monster.Pos(nil).MutateX(1.0) + monster.Pos(nil).MutateY(2.0) + monster.Pos(nil).MutateZ(3.0) + monster.Pos(nil).MutateTest1(3.0) + monster.Pos(nil).MutateTest2(example.ColorGreen) + monster.Pos(nil).Test3(nil).MutateA(5) + monster.Pos(nil).Test3(nil).MutateB(6) + monster.MutateInventory(2, 2) + + for _, t := range testForOriginalValues { + if !t.testfn() { + fail("field '" + t.field + "' doesn't have the expected original value") + } + } + + // buffer should have original values + if !reflect.DeepEqual(buf, org) { + fail("revert changes failed") + } +} + +func CheckObjectAPI(buf []byte, offset flatbuffers.UOffsetT, sizePrefix bool, fail func(string, ...interface{})) { + var monster *example.MonsterT + + if sizePrefix { + monster = example.GetSizePrefixedRootAsMonster(buf, offset).UnPack() + } else { + monster = example.GetRootAsMonster(buf, offset).UnPack() + } + + if got := monster.Hp; 80 != got { + fail(FailString("hp", 80, got)) + } + + // default + if got := monster.Mana; 150 != got { + fail(FailString("mana", 150, got)) + } + + if monster.Test != nil && monster.Test.Type == example.AnyMonster { + monster.Test.Value.(*example.MonsterT).NanDefault = 0.0 + } + if monster.Enemy != nil { + monster.Enemy.NanDefault = 0.0 + } + monster.NanDefault = 0.0 + + builder := flatbuffers.NewBuilder(0) + builder.Finish(monster.Pack(builder)) + monster2 := example.GetRootAsMonster(builder.FinishedBytes(), 0).UnPack() + if !reflect.DeepEqual(monster, monster2) { + fail(FailString("Pack/Unpack()", monster, monster2)) + } +} + +// Low level stress/fuzz test: serialize/deserialize a variety of +// different kinds of data in different combinations +func checkFuzz(fuzzFields, fuzzObjects int, fail func(string, ...interface{})) { + + // Values we're testing against: chosen to ensure no bits get chopped + // off anywhere, and also be different from eachother. + boolVal := true + int8Val := int8(-127) // 0x81 + uint8Val := uint8(0xFF) + int16Val := int16(-32222) // 0x8222 + uint16Val := uint16(0xFEEE) + int32Val := int32(overflowingInt32Val) + uint32Val := uint32(0xFDDDDDDD) + int64Val := int64(overflowingInt64Val) + uint64Val := uint64(0xFCCCCCCCCCCCCCCC) + float32Val := float32(3.14159) + float64Val := float64(3.14159265359) + + testValuesMax := 11 // hardcoded to the number of scalar types + + builder := flatbuffers.NewBuilder(0) + l := NewLCG() + + objects := make([]flatbuffers.UOffsetT, fuzzObjects) + + // Generate fuzzObjects random objects each consisting of + // fuzzFields fields, each of a random type. + for i := 0; i < fuzzObjects; i++ { + builder.StartObject(fuzzFields) + + for f := 0; f < fuzzFields; f++ { + choice := l.Next() % uint32(testValuesMax) + switch choice { + case 0: + builder.PrependBoolSlot(int(f), boolVal, false) + case 1: + builder.PrependInt8Slot(int(f), int8Val, 0) + case 2: + builder.PrependUint8Slot(int(f), uint8Val, 0) + case 3: + builder.PrependInt16Slot(int(f), int16Val, 0) + case 4: + builder.PrependUint16Slot(int(f), uint16Val, 0) + case 5: + builder.PrependInt32Slot(int(f), int32Val, 0) + case 6: + builder.PrependUint32Slot(int(f), uint32Val, 0) + case 7: + builder.PrependInt64Slot(int(f), int64Val, 0) + case 8: + builder.PrependUint64Slot(int(f), uint64Val, 0) + case 9: + builder.PrependFloat32Slot(int(f), float32Val, 0) + case 10: + builder.PrependFloat64Slot(int(f), float64Val, 0) + } + } + + off := builder.EndObject() + + // store the offset from the end of the builder buffer, + // since it will keep growing: + objects[i] = off + } + + // Do some bookkeeping to generate stats on fuzzes: + stats := map[string]int{} + check := func(desc string, want, got interface{}) { + stats[desc]++ + if want != got { + fail("%s want %v got %v", desc, want, got) + } + } + + l = NewLCG() // Reset. + + // Test that all objects we generated are readable and return the + // expected values. We generate random objects in the same order + // so this is deterministic. + for i := 0; i < fuzzObjects; i++ { + + table := &flatbuffers.Table{ + Bytes: builder.Bytes, + Pos: flatbuffers.UOffsetT(len(builder.Bytes)) - objects[i], + } + + for j := 0; j < fuzzFields; j++ { + f := flatbuffers.VOffsetT((flatbuffers.VtableMetadataFields + j) * flatbuffers.SizeVOffsetT) + choice := l.Next() % uint32(testValuesMax) + + switch choice { + case 0: + check("bool", boolVal, table.GetBoolSlot(f, false)) + case 1: + check("int8", int8Val, table.GetInt8Slot(f, 0)) + case 2: + check("uint8", uint8Val, table.GetUint8Slot(f, 0)) + case 3: + check("int16", int16Val, table.GetInt16Slot(f, 0)) + case 4: + check("uint16", uint16Val, table.GetUint16Slot(f, 0)) + case 5: + check("int32", int32Val, table.GetInt32Slot(f, 0)) + case 6: + check("uint32", uint32Val, table.GetUint32Slot(f, 0)) + case 7: + check("int64", int64Val, table.GetInt64Slot(f, 0)) + case 8: + check("uint64", uint64Val, table.GetUint64Slot(f, 0)) + case 9: + check("float32", float32Val, table.GetFloat32Slot(f, 0)) + case 10: + check("float64", float64Val, table.GetFloat64Slot(f, 0)) + } + } + } + + // If enough checks were made, verify that all scalar types were used: + if fuzzFields*fuzzObjects >= testValuesMax { + if len(stats) != testValuesMax { + fail("fuzzing failed to test all scalar types") + } + } + + // Print some counts, if needed: + if testing.Verbose() { + if fuzzFields == 0 || fuzzObjects == 0 { + fmt.Printf("fuzz\tfields: %d\tobjects: %d\t[none]\t%d\n", + fuzzFields, fuzzObjects, 0) + } else { + keys := make([]string, 0, len(stats)) + for k := range stats { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Printf("fuzz\tfields: %d\tobjects: %d\t%s\t%d\n", + fuzzFields, fuzzObjects, k, stats[k]) + } + } + } + + return +} + +// FailString makes a message for when expectations differ from reality. +func FailString(name string, want, got interface{}) string { + return fmt.Sprintf("bad %s: want %#v got %#v", name, want, got) +} + +// CheckByteLayout verifies the bytes of a Builder in various scenarios. +func CheckByteLayout(fail func(string, ...interface{})) { + var b *flatbuffers.Builder + + var i int + check := func(want []byte) { + i++ + got := b.Bytes[b.Head():] + if !bytes.Equal(want, got) { + fail("case %d: want\n%v\nbut got\n%v\n", i, want, got) + } + } + + // test 1: numbers + + b = flatbuffers.NewBuilder(0) + check([]byte{}) + b.PrependBool(true) + check([]byte{1}) + b.PrependInt8(-127) + check([]byte{129, 1}) + b.PrependUint8(255) + check([]byte{255, 129, 1}) + b.PrependInt16(-32222) + check([]byte{0x22, 0x82, 0, 255, 129, 1}) // first pad + b.PrependUint16(0xFEEE) + check([]byte{0xEE, 0xFE, 0x22, 0x82, 0, 255, 129, 1}) // no pad this time + b.PrependInt32(-53687092) + check([]byte{204, 204, 204, 252, 0xEE, 0xFE, 0x22, 0x82, 0, 255, 129, 1}) + b.PrependUint32(0x98765432) + check([]byte{0x32, 0x54, 0x76, 0x98, 204, 204, 204, 252, 0xEE, 0xFE, 0x22, 0x82, 0, 255, 129, 1}) + + // test 1b: numbers 2 + + b = flatbuffers.NewBuilder(0) + b.PrependUint64(0x1122334455667788) + check([]byte{0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11}) + + // test 2: 1xbyte vector + + b = flatbuffers.NewBuilder(0) + check([]byte{}) + b.StartVector(flatbuffers.SizeByte, 1, 1) + check([]byte{0, 0, 0}) // align to 4bytes + b.PrependByte(1) + check([]byte{1, 0, 0, 0}) + b.EndVector(1) + check([]byte{1, 0, 0, 0, 1, 0, 0, 0}) // padding + + // test 3: 2xbyte vector + + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeByte, 2, 1) + check([]byte{0, 0}) // align to 4bytes + b.PrependByte(1) + check([]byte{1, 0, 0}) + b.PrependByte(2) + check([]byte{2, 1, 0, 0}) + b.EndVector(2) + check([]byte{2, 0, 0, 0, 2, 1, 0, 0}) // padding + + // test 3b: 11xbyte vector matches builder size + + b = flatbuffers.NewBuilder(12) + b.StartVector(flatbuffers.SizeByte, 8, 1) + start := []byte{} + check(start) + for i := 1; i < 12; i++ { + b.PrependByte(byte(i)) + start = append([]byte{byte(i)}, start...) + check(start) + } + b.EndVector(8) + check(append([]byte{8, 0, 0, 0}, start...)) + + // test 4: 1xuint16 vector + + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeUint16, 1, 1) + check([]byte{0, 0}) // align to 4bytes + b.PrependUint16(1) + check([]byte{1, 0, 0, 0}) + b.EndVector(1) + check([]byte{1, 0, 0, 0, 1, 0, 0, 0}) // padding + + // test 5: 2xuint16 vector + + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeUint16, 2, 1) + check([]byte{}) // align to 4bytes + b.PrependUint16(0xABCD) + check([]byte{0xCD, 0xAB}) + b.PrependUint16(0xDCBA) + check([]byte{0xBA, 0xDC, 0xCD, 0xAB}) + b.EndVector(2) + check([]byte{2, 0, 0, 0, 0xBA, 0xDC, 0xCD, 0xAB}) + + // test 6: CreateString + + b = flatbuffers.NewBuilder(0) + b.CreateString("foo") + check([]byte{3, 0, 0, 0, 'f', 'o', 'o', 0}) // 0-terminated, no pad + b.CreateString("moop") + check([]byte{4, 0, 0, 0, 'm', 'o', 'o', 'p', 0, 0, 0, 0, // 0-terminated, 3-byte pad + 3, 0, 0, 0, 'f', 'o', 'o', 0}) + + // test 6b: CreateString unicode + + b = flatbuffers.NewBuilder(0) + // These characters are chinese from blog.golang.org/strings + // We use escape codes here so that editors without unicode support + // aren't bothered: + uni_str := "\u65e5\u672c\u8a9e" + b.CreateString(uni_str) + check([]byte{9, 0, 0, 0, 230, 151, 165, 230, 156, 172, 232, 170, 158, 0, // null-terminated, 2-byte pad + 0, 0}) + + // test 6c: CreateByteString + + b = flatbuffers.NewBuilder(0) + b.CreateByteString([]byte("foo")) + check([]byte{3, 0, 0, 0, 'f', 'o', 'o', 0}) // 0-terminated, no pad + b.CreateByteString([]byte("moop")) + check([]byte{4, 0, 0, 0, 'm', 'o', 'o', 'p', 0, 0, 0, 0, // 0-terminated, 3-byte pad + 3, 0, 0, 0, 'f', 'o', 'o', 0}) + + // test 7: empty vtable + b = flatbuffers.NewBuilder(0) + b.StartObject(0) + check([]byte{}) + b.EndObject() + check([]byte{4, 0, 4, 0, 4, 0, 0, 0}) + + // test 8: vtable with one true bool + b = flatbuffers.NewBuilder(0) + check([]byte{}) + b.StartObject(1) + check([]byte{}) + b.PrependBoolSlot(0, true, false) + b.EndObject() + check([]byte{ + 6, 0, // vtable bytes + 8, 0, // length of object including vtable offset + 7, 0, // start of bool value + 6, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, 0, // padded to 4 bytes + 1, // bool value + }) + + // test 9: vtable with one default bool + b = flatbuffers.NewBuilder(0) + check([]byte{}) + b.StartObject(1) + check([]byte{}) + b.PrependBoolSlot(0, false, false) + b.EndObject() + check([]byte{ + 4, 0, // vtable bytes + 4, 0, // end of object from here + // entry 1 is zero and not stored. + 4, 0, 0, 0, // offset for start of vtable (int32) + }) + + // test 10: vtable with one int16 + b = flatbuffers.NewBuilder(0) + b.StartObject(1) + b.PrependInt16Slot(0, 0x789A, 0) + b.EndObject() + check([]byte{ + 6, 0, // vtable bytes + 8, 0, // end of object from here + 6, 0, // offset to value + 6, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, // padding to 4 bytes + 0x9A, 0x78, + }) + + // test 11: vtable with two int16 + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt16Slot(0, 0x3456, 0) + b.PrependInt16Slot(1, 0x789A, 0) + b.EndObject() + check([]byte{ + 8, 0, // vtable bytes + 8, 0, // end of object from here + 6, 0, // offset to value 0 + 4, 0, // offset to value 1 + 8, 0, 0, 0, // offset for start of vtable (int32) + 0x9A, 0x78, // value 1 + 0x56, 0x34, // value 0 + }) + + // test 12: vtable with int16 and bool + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt16Slot(0, 0x3456, 0) + b.PrependBoolSlot(1, true, false) + b.EndObject() + check([]byte{ + 8, 0, // vtable bytes + 8, 0, // end of object from here + 6, 0, // offset to value 0 + 5, 0, // offset to value 1 + 8, 0, 0, 0, // offset for start of vtable (int32) + 0, // padding + 1, // value 1 + 0x56, 0x34, // value 0 + }) + + // test 12: vtable with empty vector + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeByte, 0, 1) + vecend := b.EndVector(0) + b.StartObject(1) + b.PrependUOffsetTSlot(0, vecend, 0) + b.EndObject() + check([]byte{ + 6, 0, // vtable bytes + 8, 0, + 4, 0, // offset to vector offset + 6, 0, 0, 0, // offset for start of vtable (int32) + 4, 0, 0, 0, + 0, 0, 0, 0, // length of vector (not in struct) + }) + + // test 12b: vtable with empty vector of byte and some scalars + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeByte, 0, 1) + vecend = b.EndVector(0) + b.StartObject(2) + b.PrependInt16Slot(0, 55, 0) + b.PrependUOffsetTSlot(1, vecend, 0) + b.EndObject() + check([]byte{ + 8, 0, // vtable bytes + 12, 0, + 10, 0, // offset to value 0 + 4, 0, // offset to vector offset + 8, 0, 0, 0, // vtable loc + 8, 0, 0, 0, // value 1 + 0, 0, 55, 0, // value 0 + + 0, 0, 0, 0, // length of vector (not in struct) + }) + + // test 13: vtable with 1 int16 and 2-vector of int16 + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeInt16, 2, 1) + b.PrependInt16(0x1234) + b.PrependInt16(0x5678) + vecend = b.EndVector(2) + b.StartObject(2) + b.PrependUOffsetTSlot(1, vecend, 0) + b.PrependInt16Slot(0, 55, 0) + b.EndObject() + check([]byte{ + 8, 0, // vtable bytes + 12, 0, // length of object + 6, 0, // start of value 0 from end of vtable + 8, 0, // start of value 1 from end of buffer + 8, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, // padding + 55, 0, // value 0 + 4, 0, 0, 0, // vector position from here + 2, 0, 0, 0, // length of vector (uint32) + 0x78, 0x56, // vector value 1 + 0x34, 0x12, // vector value 0 + }) + + // test 14: vtable with 1 struct of 1 int8, 1 int16, 1 int32 + b = flatbuffers.NewBuilder(0) + b.StartObject(1) + b.Prep(4+4+4, 0) + b.PrependInt8(55) + b.Pad(3) + b.PrependInt16(0x1234) + b.Pad(2) + b.PrependInt32(0x12345678) + structStart := b.Offset() + b.PrependStructSlot(0, structStart, 0) + b.EndObject() + check([]byte{ + 6, 0, // vtable bytes + 16, 0, // end of object from here + 4, 0, // start of struct from here + 6, 0, 0, 0, // offset for start of vtable (int32) + 0x78, 0x56, 0x34, 0x12, // value 2 + 0, 0, // padding + 0x34, 0x12, // value 1 + 0, 0, 0, // padding + 55, // value 0 + }) + + // test 15: vtable with 1 vector of 2 struct of 2 int8 + b = flatbuffers.NewBuilder(0) + b.StartVector(flatbuffers.SizeInt8*2, 2, 1) + b.PrependInt8(33) + b.PrependInt8(44) + b.PrependInt8(55) + b.PrependInt8(66) + vecend = b.EndVector(2) + b.StartObject(1) + b.PrependUOffsetTSlot(0, vecend, 0) + b.EndObject() + check([]byte{ + 6, 0, // vtable bytes + 8, 0, + 4, 0, // offset of vector offset + 6, 0, 0, 0, // offset for start of vtable (int32) + 4, 0, 0, 0, // vector start offset + + 2, 0, 0, 0, // vector length + 66, // vector value 1,1 + 55, // vector value 1,0 + 44, // vector value 0,1 + 33, // vector value 0,0 + }) + + // test 16: table with some elements + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt8Slot(0, 33, 0) + b.PrependInt16Slot(1, 66, 0) + off := b.EndObject() + b.Finish(off) + + check([]byte{ + 12, 0, 0, 0, // root of table: points to vtable offset + + 8, 0, // vtable bytes + 8, 0, // end of object from here + 7, 0, // start of value 0 + 4, 0, // start of value 1 + + 8, 0, 0, 0, // offset for start of vtable (int32) + + 66, 0, // value 1 + 0, // padding + 33, // value 0 + }) + + // test 16b: same as test 16, size prefixed + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt8Slot(0, 33, 0) + b.PrependInt16Slot(1, 66, 0) + off = b.EndObject() + b.FinishSizePrefixed(off) + + check([]byte{ + 20, 0, 0, 0, // size prefix + 12, 0, 0, 0, // root of table: points to vtable offset + + 8, 0, // vtable bytes + 8, 0, // end of object from here + 7, 0, // start of value 0 + 4, 0, // start of value 1 + + 8, 0, 0, 0, // offset for start of vtable (int32) + + 66, 0, // value 1 + 0, // padding + 33, // value 0 + }) + + // test 16c: same as test 16, with file identifier + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt8Slot(0, 33, 0) + b.PrependInt16Slot(1, 66, 0) + off = b.EndObject() + b.FinishWithFileIdentifier(off, []byte("TEST")) + + check([]byte{ + 16, 0, 0, 0, // root of table: points to vtable offset + 'T', 'E', 'S', 'T', // file identifier + + 8, 0, // vtable bytes + 8, 0, // end of object from here + 7, 0, // start of value 0 + 4, 0, // start of value 1 + + 8, 0, 0, 0, // offset for start of vtable (int32) + + 66, 0, // value 1 + 0, // padding + 33, // value 0 + }) + + // test 16d: same as test 16, size prefixed with file identifier + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt8Slot(0, 33, 0) + b.PrependInt16Slot(1, 66, 0) + off = b.EndObject() + b.FinishSizePrefixedWithFileIdentifier(off, []byte("TEST")) + + check([]byte{ + 24, 0, 0, 0, // size prefix + 16, 0, 0, 0, // root of table: points to vtable offset + 'T', 'E', 'S', 'T', // file identifier + + 8, 0, // vtable bytes + 8, 0, // end of object from here + 7, 0, // start of value 0 + 4, 0, // start of value 1 + + 8, 0, 0, 0, // offset for start of vtable (int32) + + 66, 0, // value 1 + 0, // padding + 33, // value 0 + }) + + // test 17: one unfinished table and one finished table + b = flatbuffers.NewBuilder(0) + b.StartObject(2) + b.PrependInt8Slot(0, 33, 0) + b.PrependInt8Slot(1, 44, 0) + off = b.EndObject() + b.Finish(off) + + b.StartObject(3) + b.PrependInt8Slot(0, 55, 0) + b.PrependInt8Slot(1, 66, 0) + b.PrependInt8Slot(2, 77, 0) + off = b.EndObject() + b.Finish(off) + + check([]byte{ + 16, 0, 0, 0, // root of table: points to object + 0, 0, // padding + + 10, 0, // vtable bytes + 8, 0, // size of object + 7, 0, // start of value 0 + 6, 0, // start of value 1 + 5, 0, // start of value 2 + 10, 0, 0, 0, // offset for start of vtable (int32) + 0, // padding + 77, // value 2 + 66, // value 1 + 55, // value 0 + + 12, 0, 0, 0, // root of table: points to object + + 8, 0, // vtable bytes + 8, 0, // size of object + 7, 0, // start of value 0 + 6, 0, // start of value 1 + 8, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, // padding + 44, // value 1 + 33, // value 0 + }) + + // test 18: a bunch of bools + b = flatbuffers.NewBuilder(0) + b.StartObject(8) + b.PrependBoolSlot(0, true, false) + b.PrependBoolSlot(1, true, false) + b.PrependBoolSlot(2, true, false) + b.PrependBoolSlot(3, true, false) + b.PrependBoolSlot(4, true, false) + b.PrependBoolSlot(5, true, false) + b.PrependBoolSlot(6, true, false) + b.PrependBoolSlot(7, true, false) + off = b.EndObject() + b.Finish(off) + + check([]byte{ + 24, 0, 0, 0, // root of table: points to vtable offset + + 20, 0, // vtable bytes + 12, 0, // size of object + 11, 0, // start of value 0 + 10, 0, // start of value 1 + 9, 0, // start of value 2 + 8, 0, // start of value 3 + 7, 0, // start of value 4 + 6, 0, // start of value 5 + 5, 0, // start of value 6 + 4, 0, // start of value 7 + 20, 0, 0, 0, // vtable offset + + 1, // value 7 + 1, // value 6 + 1, // value 5 + 1, // value 4 + 1, // value 3 + 1, // value 2 + 1, // value 1 + 1, // value 0 + }) + + // test 19: three bools + b = flatbuffers.NewBuilder(0) + b.StartObject(3) + b.PrependBoolSlot(0, true, false) + b.PrependBoolSlot(1, true, false) + b.PrependBoolSlot(2, true, false) + off = b.EndObject() + b.Finish(off) + + check([]byte{ + 16, 0, 0, 0, // root of table: points to vtable offset + + 0, 0, // padding + + 10, 0, // vtable bytes + 8, 0, // size of object + 7, 0, // start of value 0 + 6, 0, // start of value 1 + 5, 0, // start of value 2 + 10, 0, 0, 0, // vtable offset from here + + 0, // padding + 1, // value 2 + 1, // value 1 + 1, // value 0 + }) + + // test 20: some floats + b = flatbuffers.NewBuilder(0) + b.StartObject(1) + b.PrependFloat32Slot(0, 1.0, 0.0) + off = b.EndObject() + + check([]byte{ + 6, 0, // vtable bytes + 8, 0, // size of object + 4, 0, // start of value 0 + 6, 0, 0, 0, // vtable offset + + 0, 0, 128, 63, // value 0 + }) +} + +// CheckManualBuild builds a Monster manually. +func CheckManualBuild(fail func(string, ...interface{})) ([]byte, flatbuffers.UOffsetT) { + b := flatbuffers.NewBuilder(0) + str := b.CreateString("MyMonster") + + b.StartVector(1, 5, 1) + b.PrependByte(4) + b.PrependByte(3) + b.PrependByte(2) + b.PrependByte(1) + b.PrependByte(0) + inv := b.EndVector(5) + + b.StartObject(13) + b.PrependInt16Slot(2, 20, 100) + mon2 := b.EndObject() + + // Test4Vector + b.StartVector(4, 2, 1) + + // Test 0 + b.Prep(2, 4) + b.Pad(1) + b.PlaceInt8(20) + b.PlaceInt16(10) + + // Test 1 + b.Prep(2, 4) + b.Pad(1) + b.PlaceInt8(40) + b.PlaceInt16(30) + + // end testvector + test4 := b.EndVector(2) + + b.StartObject(13) + + // a vec3 + b.Prep(16, 32) + b.Pad(2) + b.Prep(2, 4) + b.Pad(1) + b.PlaceByte(6) + b.PlaceInt16(5) + b.Pad(1) + b.PlaceByte(4) + b.PlaceFloat64(3.0) + b.Pad(4) + b.PlaceFloat32(3.0) + b.PlaceFloat32(2.0) + b.PlaceFloat32(1.0) + vec3Loc := b.Offset() + // end vec3 + + b.PrependStructSlot(0, vec3Loc, 0) // vec3. noop + b.PrependInt16Slot(2, 80, 100) // hp + b.PrependUOffsetTSlot(3, str, 0) + b.PrependUOffsetTSlot(5, inv, 0) // inventory + b.PrependByteSlot(7, 1, 0) + b.PrependUOffsetTSlot(8, mon2, 0) + b.PrependUOffsetTSlot(9, test4, 0) + mon := b.EndObject() + + b.Finish(mon) + + return b.Bytes, b.Head() +} + +func CheckGetRootAsForNonRootTable(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + str := b.CreateString("MyStat") + example.StatStart(b) + example.StatAddid(b, str) + example.StatAddval(b, 12345678) + example.StatAddcount(b, 12345) + stat_end := example.StatEnd(b) + b.Finish(stat_end) + + stat := example.GetRootAsStat(b.Bytes, b.Head()) + + if got := stat.Id(); !bytes.Equal([]byte("MyStat"), got) { + fail(FailString("stat.Id()", "MyStat", got)) + } + + if got := stat.Val(); 12345678 != got { + fail(FailString("stat.Val()", 12345678, got)) + } + + if got := stat.Count(); 12345 != got { + fail(FailString("stat.Count()", 12345, got)) + } +} + +// CheckGeneratedBuild uses generated code to build the example Monster. +func CheckGeneratedBuild(sizePrefix, fileIdentifier bool, fail func(string, ...interface{})) ([]byte, flatbuffers.UOffsetT) { + b := flatbuffers.NewBuilder(0) + str := b.CreateString("MyMonster") + test1 := b.CreateString("test1") + test2 := b.CreateString("test2") + fred := b.CreateString("Fred") + + example.MonsterStartinventoryVector(b, 5) + b.PrependByte(4) + b.PrependByte(3) + b.PrependByte(2) + b.PrependByte(1) + b.PrependByte(0) + inv := b.EndVector(5) + + example.MonsterStart(b) + example.MonsterAddname(b, fred) + mon2 := example.MonsterEnd(b) + + example.MonsterStarttest4Vector(b, 2) + example.CreateTest(b, 10, 20) + example.CreateTest(b, 30, 40) + test4 := b.EndVector(2) + + example.MonsterStarttestarrayofstringVector(b, 2) + b.PrependUOffsetT(test2) + b.PrependUOffsetT(test1) + testArrayOfString := b.EndVector(2) + + example.MonsterStart(b) + + pos := example.CreateVec3(b, 1.0, 2.0, 3.0, 3.0, example.ColorGreen, 5, 6) + example.MonsterAddpos(b, pos) + + example.MonsterAddhp(b, 80) + example.MonsterAddname(b, str) + example.MonsterAddtestbool(b, true) + example.MonsterAddinventory(b, inv) + example.MonsterAddtest_type(b, 1) + example.MonsterAddtest(b, mon2) + example.MonsterAddtest4(b, test4) + example.MonsterAddtestarrayofstring(b, testArrayOfString) + mon := example.MonsterEnd(b) + + if fileIdentifier { + if sizePrefix { + example.FinishSizePrefixedMonsterBuffer(b, mon) + } else { + example.FinishMonsterBuffer(b, mon) + } + } else { + if sizePrefix { + b.FinishSizePrefixed(mon) + } else { + b.Finish(mon) + } + } + + return b.Bytes, b.Head() +} + +// CheckTableAccessors checks that the table accessors work as expected. +func CheckTableAccessors(fail func(string, ...interface{})) { + // test struct accessor + b := flatbuffers.NewBuilder(0) + pos := example.CreateVec3(b, 1.0, 2.0, 3.0, 3.0, 4, 5, 6) + b.Finish(pos) + vec3Bytes := b.FinishedBytes() + vec3 := &example.Vec3{} + flatbuffers.GetRootAs(vec3Bytes, 0, vec3) + + if bytes.Compare(vec3Bytes, vec3.Table().Bytes) != 0 { + fail("invalid vec3 table") + } + + // test table accessor + b = flatbuffers.NewBuilder(0) + str := b.CreateString("MyStat") + example.StatStart(b) + example.StatAddid(b, str) + example.StatAddval(b, 12345678) + example.StatAddcount(b, 12345) + pos = example.StatEnd(b) + b.Finish(pos) + statBytes := b.FinishedBytes() + stat := &example.Stat{} + flatbuffers.GetRootAs(statBytes, 0, stat) + + if bytes.Compare(statBytes, stat.Table().Bytes) != 0 { + fail("invalid stat table") + } +} + +// CheckVtableDeduplication verifies that vtables are deduplicated. +func CheckVtableDeduplication(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + + b.StartObject(4) + b.PrependByteSlot(0, 0, 0) + b.PrependByteSlot(1, 11, 0) + b.PrependByteSlot(2, 22, 0) + b.PrependInt16Slot(3, 33, 0) + obj0 := b.EndObject() + + b.StartObject(4) + b.PrependByteSlot(0, 0, 0) + b.PrependByteSlot(1, 44, 0) + b.PrependByteSlot(2, 55, 0) + b.PrependInt16Slot(3, 66, 0) + obj1 := b.EndObject() + + b.StartObject(4) + b.PrependByteSlot(0, 0, 0) + b.PrependByteSlot(1, 77, 0) + b.PrependByteSlot(2, 88, 0) + b.PrependInt16Slot(3, 99, 0) + obj2 := b.EndObject() + + got := b.Bytes[b.Head():] + + want := []byte{ + 240, 255, 255, 255, // == -12. offset to dedupped vtable. + 99, 0, + 88, + 77, + 248, 255, 255, 255, // == -8. offset to dedupped vtable. + 66, 0, + 55, + 44, + 12, 0, + 8, 0, + 0, 0, + 7, 0, + 6, 0, + 4, 0, + 12, 0, 0, 0, + 33, 0, + 22, + 11, + } + + if !bytes.Equal(want, got) { + fail("testVtableDeduplication want:\n%d %v\nbut got:\n%d %v\n", + len(want), want, len(got), got) + } + + table0 := &flatbuffers.Table{Bytes: b.Bytes, Pos: flatbuffers.UOffsetT(len(b.Bytes)) - obj0} + table1 := &flatbuffers.Table{Bytes: b.Bytes, Pos: flatbuffers.UOffsetT(len(b.Bytes)) - obj1} + table2 := &flatbuffers.Table{Bytes: b.Bytes, Pos: flatbuffers.UOffsetT(len(b.Bytes)) - obj2} + + testTable := func(tab *flatbuffers.Table, a flatbuffers.VOffsetT, b, c, d byte) { + // vtable size + if got := tab.GetVOffsetTSlot(0, 0); 12 != got { + fail("failed 0, 0: %d", got) + } + // object size + if got := tab.GetVOffsetTSlot(2, 0); 8 != got { + fail("failed 2, 0: %d", got) + } + // default value + if got := tab.GetVOffsetTSlot(4, 0); a != got { + fail("failed 4, 0: %d", got) + } + if got := tab.GetByteSlot(6, 0); b != got { + fail("failed 6, 0: %d", got) + } + if val := tab.GetByteSlot(8, 0); c != val { + fail("failed 8, 0: %d", got) + } + if got := tab.GetByteSlot(10, 0); d != got { + fail("failed 10, 0: %d", got) + } + } + + testTable(table0, 0, 11, 22, 33) + testTable(table1, 0, 44, 55, 66) + testTable(table2, 0, 77, 88, 99) +} + +// CheckNotInObjectError verifies that `EndObject` fails if not inside an +// object. +func CheckNotInObjectError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckNotInObjectError") + } + }() + b.EndObject() +} + +// CheckStringIsNestedError verifies that a string can not be created inside +// another object. +func CheckStringIsNestedError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckStringIsNestedError") + } + }() + b.CreateString("foo") +} + +func CheckEmptiedBuilder(fail func(string, ...interface{})) { + f := func(a, b string) bool { + if a == b { + return true + } + + builder := flatbuffers.NewBuilder(0) + + a1 := builder.CreateSharedString(a) + b1 := builder.CreateSharedString(b) + builder.Reset() + b2 := builder.CreateSharedString(b) + a2 := builder.CreateSharedString(a) + + return !(a1 == a2 || b1 == b2) + } + if err := quick.Check(f, nil); err != nil { + fail("expected different offset") + } +} + +func CheckSharedStrings(fail func(string, ...interface{})) { + f := func(strings []string) bool { + b := flatbuffers.NewBuilder(0) + for _, s1 := range strings { + for _, s2 := range strings { + off1 := b.CreateSharedString(s1) + off2 := b.CreateSharedString(s2) + + if (s1 == s2) && (off1 != off2) { + return false + } + if (s1 != s2) && (off1 == off2) { + return false + } + } + } + return true + } + if err := quick.Check(f, nil); err != nil { + fail("expected same offset") + } +} + +// CheckByteStringIsNestedError verifies that a bytestring can not be created +// inside another object. +func CheckByteStringIsNestedError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckByteStringIsNestedError") + } + }() + b.CreateByteString([]byte("foo")) +} + +// CheckStructIsNotInlineError verifies that writing a struct in a location +// away from where it is used will cause a panic. +func CheckStructIsNotInlineError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(0) + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckStructIsNotInlineError") + } + }() + b.PrependStructSlot(0, 1, 0) +} + +// CheckFinishedBytesError verifies that `FinishedBytes` panics if the table +// is not finished. +func CheckFinishedBytesError(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + + defer func() { + r := recover() + if r == nil { + fail("expected panic in CheckFinishedBytesError") + } + }() + b.FinishedBytes() +} + +// CheckEnumNames checks that the generated enum names are correct. +func CheckEnumNames(fail func(string, ...interface{})) { + { + want := map[example.Any]string{ + example.AnyNONE: "NONE", + example.AnyMonster: "Monster", + example.AnyTestSimpleTableWithEnum: "TestSimpleTableWithEnum", + example.AnyMyGame_Example2_Monster: "MyGame_Example2_Monster", + } + got := example.EnumNamesAny + if !reflect.DeepEqual(got, want) { + fail("enum name is not equal") + } + } + { + want := map[example.Color]string{ + example.ColorRed: "Red", + example.ColorGreen: "Green", + example.ColorBlue: "Blue", + } + got := example.EnumNamesColor + if !reflect.DeepEqual(got, want) { + fail("enum name is not equal") + } + } +} + +// CheckEnumString checks the String method on generated enum types. +func CheckEnumString(fail func(string, ...interface{})) { + if got := example.AnyMonster.String(); got != "Monster" { + fail("Monster.String: %q != %q", got, "Monster") + } + if got := fmt.Sprintf("color: %s", example.ColorGreen); got != "color: Green" { + fail("color.String: %q != %q", got, "color: Green") + } +} + +// CheckEnumValues checks that the generated enum values maps are correct. +func CheckEnumValues(fail func(string, ...interface{})) { + { + want := map[string]example.Any{ + "NONE": example.AnyNONE, + "Monster": example.AnyMonster, + "TestSimpleTableWithEnum": example.AnyTestSimpleTableWithEnum, + "MyGame_Example2_Monster": example.AnyMyGame_Example2_Monster, + } + got := example.EnumValuesAny + if !reflect.DeepEqual(got, want) { + fail("enum name is not equal") + } + } + { + want := map[string]example.Color{ + "Red": example.ColorRed, + "Green": example.ColorGreen, + "Blue": example.ColorBlue, + } + got := example.EnumValuesColor + if !reflect.DeepEqual(got, want) { + fail("enum name is not equal") + } + } +} + +// CheckDocExample checks that the code given in FlatBuffers documentation +// is syntactically correct. +func CheckDocExample(buf []byte, off flatbuffers.UOffsetT, fail func(string, ...interface{})) { + monster := example.GetRootAsMonster(buf, off) + _ = monster.Hp() + _ = monster.Pos(nil) + for i := 0; i < monster.InventoryLength(); i++ { + _ = monster.Inventory(i) // do something here + } + + builder := flatbuffers.NewBuilder(0) + + example.MonsterStartinventoryVector(builder, 5) + for i := 4; i >= 0; i-- { + builder.PrependByte(byte(i)) + } + inv := builder.EndVector(5) + + str := builder.CreateString("MyMonster") + example.MonsterStart(builder) + example.MonsterAddpos(builder, example.CreateVec3(builder, 1.0, 2.0, 3.0, 3.0, example.Color(4), 5, 6)) + example.MonsterAddhp(builder, 80) + example.MonsterAddname(builder, str) + example.MonsterAddinventory(builder, inv) + example.MonsterAddtest_type(builder, 1) + example.MonsterAddcolor(builder, example.ColorRed) + // example.MonsterAddtest(builder, mon2) + // example.MonsterAddtest4(builder, test4s) + _ = example.MonsterEnd(builder) +} + +func CheckCreateByteVector(fail func(string, ...interface{})) { + raw := [30]byte{} + for i := 0; i < len(raw); i++ { + raw[i] = byte(i) + } + + for size := 0; size < len(raw); size++ { + b1 := flatbuffers.NewBuilder(0) + b2 := flatbuffers.NewBuilder(0) + b1.StartVector(1, size, 1) + for i := size - 1; i >= 0; i-- { + b1.PrependByte(raw[i]) + } + b1.EndVector(size) + b2.CreateByteVector(raw[:size]) + CheckByteEquality(b1.Bytes, b2.Bytes, fail) + } +} + +func CheckParentNamespace(fail func(string, ...interface{})) { + var empty, nonempty []byte + + // create monster with an empty parent namespace field + { + builder := flatbuffers.NewBuilder(0) + + example.MonsterStart(builder) + m := example.MonsterEnd(builder) + builder.Finish(m) + + empty = make([]byte, len(builder.FinishedBytes())) + copy(empty, builder.FinishedBytes()) + } + + // create monster with a non-empty parent namespace field + { + builder := flatbuffers.NewBuilder(0) + mygame.InParentNamespaceStart(builder) + pn := mygame.InParentNamespaceEnd(builder) + + example.MonsterStart(builder) + example.MonsterAddparent_namespace_test(builder, pn) + m := example.MonsterEnd(builder) + + builder.Finish(m) + + nonempty = make([]byte, len(builder.FinishedBytes())) + copy(nonempty, builder.FinishedBytes()) + } + + // read monster with empty parent namespace field + { + m := example.GetRootAsMonster(empty, 0) + if m.ParentNamespaceTest(nil) != nil { + fail("expected nil ParentNamespaceTest for empty field") + } + } + + // read monster with non-empty parent namespace field + { + m := example.GetRootAsMonster(nonempty, 0) + if m.ParentNamespaceTest(nil) == nil { + fail("expected non-nil ParentNamespaceTest for non-empty field") + } + } +} + +func CheckSizePrefixedBuffer(fail func(string, ...interface{})) { + // Generate a size-prefixed flatbuffer, first without file identifier + generated, off := CheckGeneratedBuild(true, false, fail) + + // Check that the buffer can be used as expected + CheckReadBuffer(generated, off, true, fail) + CheckMutateBuffer(generated, off, true, fail) + CheckObjectAPI(generated, off, true, fail) + + // Now generate a size-prefixed flatbuffer with file identifier + generated, off = CheckGeneratedBuild(true, true, fail) + + // Check that the size prefix is the size of monsterdata_go_wire.mon, + // plus 4 bytes for padding + size := flatbuffers.GetSizePrefix(generated, off) + expectedSize := uint32(228) + if size != expectedSize { + fail("mismatch between size prefix (%d) and expected size (%d)", size, expectedSize) + } + + // Check that the buffer can be used as expected + CheckReadBuffer(generated, off, true, fail) + CheckMutateBuffer(generated, off, true, fail) + CheckObjectAPI(generated, off, true, fail) + CheckFileIdentifier(generated, off, true, fail) + + // Write generated buffer out to a file + if err := os.WriteFile(outData+".sp", generated[off:], os.FileMode(0644)); err != nil { + fail("failed to write file: %s", err) + } +} + +// Include simple random number generator to ensure results will be the +// same cross platform. +// http://en.wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator +type LCG uint32 + +const InitialLCGSeed = 48271 + +func NewLCG() *LCG { + n := uint32(InitialLCGSeed) + l := LCG(n) + return &l +} + +func (lcg *LCG) Reset() { + *lcg = InitialLCGSeed +} + +func (lcg *LCG) Next() uint32 { + n := uint32((uint64(*lcg) * uint64(279470273)) % uint64(4294967291)) + *lcg = LCG(n) + return n +} + +// CheckByteEquality verifies that two byte buffers are the same. +func CheckByteEquality(a, b []byte, fail func(string, ...interface{})) { + if !bytes.Equal(a, b) { + fail("objects are not byte-wise equal") + } +} + +// CheckMutateMethods checks all mutate methods one by one +func CheckMutateMethods(fail func(string, ...interface{})) { + b := flatbuffers.NewBuilder(0) + b.StartObject(15) + b.PrependBoolSlot(0, true, false) + b.PrependByteSlot(1, 1, 0) + b.PrependUint8Slot(2, 2, 0) + b.PrependUint16Slot(3, 3, 0) + b.PrependUint32Slot(4, 4, 0) + b.PrependUint64Slot(5, 5, 0) + b.PrependInt8Slot(6, 6, 0) + b.PrependInt16Slot(7, 7, 0) + b.PrependInt32Slot(8, 8, 0) + b.PrependInt64Slot(9, 9, 0) + b.PrependFloat32Slot(10, 10, 0) + b.PrependFloat64Slot(11, 11, 0) + + b.PrependUOffsetTSlot(12, 12, 0) + uoVal := b.Offset() - 12 + + b.PrependVOffsetT(13) + b.Slot(13) + + b.PrependSOffsetT(14) + b.Slot(14) + soVal := flatbuffers.SOffsetT(b.Offset() - 14) + + offset := b.EndObject() + + t := &flatbuffers.Table{ + Bytes: b.Bytes, + Pos: flatbuffers.UOffsetT(len(b.Bytes)) - offset, + } + + calcVOffsetT := func(slot int) (vtableOffset flatbuffers.VOffsetT) { + return flatbuffers.VOffsetT((flatbuffers.VtableMetadataFields + slot) * flatbuffers.SizeVOffsetT) + } + calcUOffsetT := func(vtableOffset flatbuffers.VOffsetT) (valueOffset flatbuffers.UOffsetT) { + return t.Pos + flatbuffers.UOffsetT(t.Offset(vtableOffset)) + } + + type testcase struct { + field string + testfn func() bool + } + + testForOriginalValues := []testcase{ + testcase{"BoolSlot", func() bool { return t.GetBoolSlot(calcVOffsetT(0), true) == true }}, + testcase{"ByteSlot", func() bool { return t.GetByteSlot(calcVOffsetT(1), 1) == 1 }}, + testcase{"Uint8Slot", func() bool { return t.GetUint8Slot(calcVOffsetT(2), 2) == 2 }}, + testcase{"Uint16Slot", func() bool { return t.GetUint16Slot(calcVOffsetT(3), 3) == 3 }}, + testcase{"Uint32Slot", func() bool { return t.GetUint32Slot(calcVOffsetT(4), 4) == 4 }}, + testcase{"Uint64Slot", func() bool { return t.GetUint64Slot(calcVOffsetT(5), 5) == 5 }}, + testcase{"Int8Slot", func() bool { return t.GetInt8Slot(calcVOffsetT(6), 6) == 6 }}, + testcase{"Int16Slot", func() bool { return t.GetInt16Slot(calcVOffsetT(7), 7) == 7 }}, + testcase{"Int32Slot", func() bool { return t.GetInt32Slot(calcVOffsetT(8), 8) == 8 }}, + testcase{"Int64Slot", func() bool { return t.GetInt64Slot(calcVOffsetT(9), 9) == 9 }}, + testcase{"Float32Slot", func() bool { return t.GetFloat32Slot(calcVOffsetT(10), 10) == 10 }}, + testcase{"Float64Slot", func() bool { return t.GetFloat64Slot(calcVOffsetT(11), 11) == 11 }}, + testcase{"UOffsetTSlot", func() bool { return t.GetUOffsetT(calcUOffsetT(calcVOffsetT(12))) == uoVal }}, + testcase{"VOffsetTSlot", func() bool { return t.GetVOffsetT(calcUOffsetT(calcVOffsetT(13))) == 13 }}, + testcase{"SOffsetTSlot", func() bool { return t.GetSOffsetT(calcUOffsetT(calcVOffsetT(14))) == soVal }}, + } + + testMutability := []testcase{ + testcase{"BoolSlot", func() bool { return t.MutateBoolSlot(calcVOffsetT(0), false) }}, + testcase{"ByteSlot", func() bool { return t.MutateByteSlot(calcVOffsetT(1), 2) }}, + testcase{"Uint8Slot", func() bool { return t.MutateUint8Slot(calcVOffsetT(2), 4) }}, + testcase{"Uint16Slot", func() bool { return t.MutateUint16Slot(calcVOffsetT(3), 6) }}, + testcase{"Uint32Slot", func() bool { return t.MutateUint32Slot(calcVOffsetT(4), 8) }}, + testcase{"Uint64Slot", func() bool { return t.MutateUint64Slot(calcVOffsetT(5), 10) }}, + testcase{"Int8Slot", func() bool { return t.MutateInt8Slot(calcVOffsetT(6), 12) }}, + testcase{"Int16Slot", func() bool { return t.MutateInt16Slot(calcVOffsetT(7), 14) }}, + testcase{"Int32Slot", func() bool { return t.MutateInt32Slot(calcVOffsetT(8), 16) }}, + testcase{"Int64Slot", func() bool { return t.MutateInt64Slot(calcVOffsetT(9), 18) }}, + testcase{"Float32Slot", func() bool { return t.MutateFloat32Slot(calcVOffsetT(10), 20) }}, + testcase{"Float64Slot", func() bool { return t.MutateFloat64Slot(calcVOffsetT(11), 22) }}, + testcase{"UOffsetTSlot", func() bool { return t.MutateUOffsetT(calcUOffsetT(calcVOffsetT(12)), 24) }}, + testcase{"VOffsetTSlot", func() bool { return t.MutateVOffsetT(calcUOffsetT(calcVOffsetT(13)), 26) }}, + testcase{"SOffsetTSlot", func() bool { return t.MutateSOffsetT(calcUOffsetT(calcVOffsetT(14)), 28) }}, + } + + testMutabilityWithoutSlot := []testcase{ + testcase{"BoolSlot", func() bool { return t.MutateBoolSlot(calcVOffsetT(16), false) }}, + testcase{"ByteSlot", func() bool { return t.MutateByteSlot(calcVOffsetT(16), 2) }}, + testcase{"Uint8Slot", func() bool { return t.MutateUint8Slot(calcVOffsetT(16), 2) }}, + testcase{"Uint16Slot", func() bool { return t.MutateUint16Slot(calcVOffsetT(16), 2) }}, + testcase{"Uint32Slot", func() bool { return t.MutateUint32Slot(calcVOffsetT(16), 2) }}, + testcase{"Uint64Slot", func() bool { return t.MutateUint64Slot(calcVOffsetT(16), 2) }}, + testcase{"Int8Slot", func() bool { return t.MutateInt8Slot(calcVOffsetT(16), 2) }}, + testcase{"Int16Slot", func() bool { return t.MutateInt16Slot(calcVOffsetT(16), 2) }}, + testcase{"Int32Slot", func() bool { return t.MutateInt32Slot(calcVOffsetT(16), 2) }}, + testcase{"Int64Slot", func() bool { return t.MutateInt64Slot(calcVOffsetT(16), 2) }}, + testcase{"Float32Slot", func() bool { return t.MutateFloat32Slot(calcVOffsetT(16), 2) }}, + testcase{"Float64Slot", func() bool { return t.MutateFloat64Slot(calcVOffsetT(16), 2) }}, + } + + testForMutatedValues := []testcase{ + testcase{"BoolSlot", func() bool { return t.GetBoolSlot(calcVOffsetT(0), true) == false }}, + testcase{"ByteSlot", func() bool { return t.GetByteSlot(calcVOffsetT(1), 1) == 2 }}, + testcase{"Uint8Slot", func() bool { return t.GetUint8Slot(calcVOffsetT(2), 1) == 4 }}, + testcase{"Uint16Slot", func() bool { return t.GetUint16Slot(calcVOffsetT(3), 1) == 6 }}, + testcase{"Uint32Slot", func() bool { return t.GetUint32Slot(calcVOffsetT(4), 1) == 8 }}, + testcase{"Uint64Slot", func() bool { return t.GetUint64Slot(calcVOffsetT(5), 1) == 10 }}, + testcase{"Int8Slot", func() bool { return t.GetInt8Slot(calcVOffsetT(6), 1) == 12 }}, + testcase{"Int16Slot", func() bool { return t.GetInt16Slot(calcVOffsetT(7), 1) == 14 }}, + testcase{"Int32Slot", func() bool { return t.GetInt32Slot(calcVOffsetT(8), 1) == 16 }}, + testcase{"Int64Slot", func() bool { return t.GetInt64Slot(calcVOffsetT(9), 1) == 18 }}, + testcase{"Float32Slot", func() bool { return t.GetFloat32Slot(calcVOffsetT(10), 1) == 20 }}, + testcase{"Float64Slot", func() bool { return t.GetFloat64Slot(calcVOffsetT(11), 1) == 22 }}, + testcase{"UOffsetTSlot", func() bool { return t.GetUOffsetT(calcUOffsetT(calcVOffsetT(12))) == 24 }}, + testcase{"VOffsetTSlot", func() bool { return t.GetVOffsetT(calcUOffsetT(calcVOffsetT(13))) == 26 }}, + testcase{"SOffsetTSlot", func() bool { return t.GetSOffsetT(calcUOffsetT(calcVOffsetT(14))) == 28 }}, + } + + // make sure original values are okay + for _, t := range testForOriginalValues { + if !t.testfn() { + fail(t.field + "' field doesn't have the expected original value") + } + } + + // try to mutate fields and check mutability + for _, t := range testMutability { + if !t.testfn() { + fail(FailString(t.field+"' field failed mutability test", "passed", "failed")) + } + } + + // try to mutate fields and check mutability + // these have wrong slots so should fail + for _, t := range testMutabilityWithoutSlot { + if t.testfn() { + fail(FailString(t.field+"' field failed no slot mutability test", "failed", "passed")) + } + } + + // test whether values have changed + for _, t := range testForMutatedValues { + if !t.testfn() { + fail(t.field + "' field doesn't have the expected mutated value") + } + } +} + +// CheckOptionalScalars verifies against the ScalarStuff schema. +func CheckOptionalScalars(fail func(string, ...interface{})) { + type testCase struct { + what string + result, expect interface{} + } + + makeDefaultTestCases := func(s *optional_scalars.ScalarStuff) []testCase { + return []testCase{ + {"justI8", s.JustI8(), int8(0)}, + {"maybeI8", s.MaybeI8(), (*int8)(nil)}, + {"defaultI8", s.DefaultI8(), int8(42)}, + {"justU8", s.JustU8(), byte(0)}, + {"maybeU8", s.MaybeU8(), (*byte)(nil)}, + {"defaultU8", s.DefaultU8(), byte(42)}, + {"justI16", s.JustI16(), int16(0)}, + {"maybeI16", s.MaybeI16(), (*int16)(nil)}, + {"defaultI16", s.DefaultI16(), int16(42)}, + {"justU16", s.JustU16(), uint16(0)}, + {"maybeU16", s.MaybeU16(), (*uint16)(nil)}, + {"defaultU16", s.DefaultU16(), uint16(42)}, + {"justI32", s.JustI32(), int32(0)}, + {"maybeI32", s.MaybeI32(), (*int32)(nil)}, + {"defaultI32", s.DefaultI32(), int32(42)}, + {"justU32", s.JustU32(), uint32(0)}, + {"maybeU32", s.MaybeU32(), (*uint32)(nil)}, + {"defaultU32", s.DefaultU32(), uint32(42)}, + {"justI64", s.JustI64(), int64(0)}, + {"maybeI64", s.MaybeI64(), (*int64)(nil)}, + {"defaultI64", s.DefaultI64(), int64(42)}, + {"justU64", s.JustU64(), uint64(0)}, + {"maybeU64", s.MaybeU64(), (*uint64)(nil)}, + {"defaultU64", s.DefaultU64(), uint64(42)}, + {"justF32", s.JustF32(), float32(0)}, + {"maybeF32", s.MaybeF32(), (*float32)(nil)}, + {"defaultF32", s.DefaultF32(), float32(42)}, + {"justF64", s.JustF64(), float64(0)}, + {"maybeF64", s.MaybeF64(), (*float64)(nil)}, + {"defaultF64", s.DefaultF64(), float64(42)}, + {"justBool", s.JustBool(), false}, + {"maybeBool", s.MaybeBool(), (*bool)(nil)}, + {"defaultBool", s.DefaultBool(), true}, + {"justEnum", s.JustEnum(), optional_scalars.OptionalByte(0)}, + {"maybeEnum", s.MaybeEnum(), (*optional_scalars.OptionalByte)(nil)}, + {"defaultEnum", s.DefaultEnum(), optional_scalars.OptionalByteOne}, + } + } + + makeAssignedTestCases := func(s *optional_scalars.ScalarStuff) []testCase { + return []testCase{ + {"justI8", s.JustI8(), int8(5)}, + {"maybeI8", s.MaybeI8(), int8(5)}, + {"defaultI8", s.DefaultI8(), int8(5)}, + {"justU8", s.JustU8(), byte(6)}, + {"maybeU8", s.MaybeU8(), byte(6)}, + {"defaultU8", s.DefaultU8(), byte(6)}, + {"justI16", s.JustI16(), int16(7)}, + {"maybeI16", s.MaybeI16(), int16(7)}, + {"defaultI16", s.DefaultI16(), int16(7)}, + {"justU16", s.JustU16(), uint16(8)}, + {"maybeU16", s.MaybeU16(), uint16(8)}, + {"defaultU16", s.DefaultU16(), uint16(8)}, + {"justI32", s.JustI32(), int32(9)}, + {"maybeI32", s.MaybeI32(), int32(9)}, + {"defaultI32", s.DefaultI32(), int32(9)}, + {"justU32", s.JustU32(), uint32(10)}, + {"maybeU32", s.MaybeU32(), uint32(10)}, + {"defaultU32", s.DefaultU32(), uint32(10)}, + {"justI64", s.JustI64(), int64(11)}, + {"maybeI64", s.MaybeI64(), int64(11)}, + {"defaultI64", s.DefaultI64(), int64(11)}, + {"justU64", s.JustU64(), uint64(12)}, + {"maybeU64", s.MaybeU64(), uint64(12)}, + {"defaultU64", s.DefaultU64(), uint64(12)}, + {"justF32", s.JustF32(), float32(13)}, + {"maybeF32", s.MaybeF32(), float32(13)}, + {"defaultF32", s.DefaultF32(), float32(13)}, + {"justF64", s.JustF64(), float64(14)}, + {"maybeF64", s.MaybeF64(), float64(14)}, + {"defaultF64", s.DefaultF64(), float64(14)}, + {"justBool", s.JustBool(), true}, + {"maybeBool", s.MaybeBool(), true}, + {"defaultBool", s.DefaultBool(), false}, + {"justEnum", s.JustEnum(), optional_scalars.OptionalByteTwo}, + {"maybeEnum", s.MaybeEnum(), optional_scalars.OptionalByteTwo}, + {"defaultEnum", s.DefaultEnum(), optional_scalars.OptionalByteTwo}, + } + } + + resolvePointer := func(v interface{}) interface{} { + switch v := v.(type) { + case *int8: + return *v + case *byte: + return *v + case *int16: + return *v + case *uint16: + return *v + case *int32: + return *v + case *uint32: + return *v + case *int64: + return *v + case *uint64: + return *v + case *float32: + return *v + case *float64: + return *v + case *bool: + return *v + case *optional_scalars.OptionalByte: + return *v + default: + return v + } + } + + buildAssignedTable := func(b *flatbuffers.Builder) *optional_scalars.ScalarStuff { + optional_scalars.ScalarStuffStart(b) + optional_scalars.ScalarStuffAddJustI8(b, int8(5)) + optional_scalars.ScalarStuffAddMaybeI8(b, int8(5)) + optional_scalars.ScalarStuffAddDefaultI8(b, int8(5)) + optional_scalars.ScalarStuffAddJustU8(b, byte(6)) + optional_scalars.ScalarStuffAddMaybeU8(b, byte(6)) + optional_scalars.ScalarStuffAddDefaultU8(b, byte(6)) + optional_scalars.ScalarStuffAddJustI16(b, int16(7)) + optional_scalars.ScalarStuffAddMaybeI16(b, int16(7)) + optional_scalars.ScalarStuffAddDefaultI16(b, int16(7)) + optional_scalars.ScalarStuffAddJustU16(b, uint16(8)) + optional_scalars.ScalarStuffAddMaybeU16(b, uint16(8)) + optional_scalars.ScalarStuffAddDefaultU16(b, uint16(8)) + optional_scalars.ScalarStuffAddJustI32(b, int32(9)) + optional_scalars.ScalarStuffAddMaybeI32(b, int32(9)) + optional_scalars.ScalarStuffAddDefaultI32(b, int32(9)) + optional_scalars.ScalarStuffAddJustU32(b, uint32(10)) + optional_scalars.ScalarStuffAddMaybeU32(b, uint32(10)) + optional_scalars.ScalarStuffAddDefaultU32(b, uint32(10)) + optional_scalars.ScalarStuffAddJustI64(b, int64(11)) + optional_scalars.ScalarStuffAddMaybeI64(b, int64(11)) + optional_scalars.ScalarStuffAddDefaultI64(b, int64(11)) + optional_scalars.ScalarStuffAddJustU64(b, uint64(12)) + optional_scalars.ScalarStuffAddMaybeU64(b, uint64(12)) + optional_scalars.ScalarStuffAddDefaultU64(b, uint64(12)) + optional_scalars.ScalarStuffAddJustF32(b, float32(13)) + optional_scalars.ScalarStuffAddMaybeF32(b, float32(13)) + optional_scalars.ScalarStuffAddDefaultF32(b, float32(13)) + optional_scalars.ScalarStuffAddJustF64(b, float64(14)) + optional_scalars.ScalarStuffAddMaybeF64(b, float64(14)) + optional_scalars.ScalarStuffAddDefaultF64(b, float64(14)) + optional_scalars.ScalarStuffAddJustBool(b, true) + optional_scalars.ScalarStuffAddMaybeBool(b, true) + optional_scalars.ScalarStuffAddDefaultBool(b, false) + optional_scalars.ScalarStuffAddJustEnum(b, optional_scalars.OptionalByteTwo) + optional_scalars.ScalarStuffAddMaybeEnum(b, optional_scalars.OptionalByteTwo) + optional_scalars.ScalarStuffAddDefaultEnum(b, optional_scalars.OptionalByteTwo) + b.Finish(optional_scalars.ScalarStuffEnd(b)) + return optional_scalars.GetRootAsScalarStuff(b.FinishedBytes(), 0) + } + + // test default values + + fbb := flatbuffers.NewBuilder(1) + optional_scalars.ScalarStuffStart(fbb) + fbb.Finish(optional_scalars.ScalarStuffEnd(fbb)) + ss := optional_scalars.GetRootAsScalarStuff(fbb.FinishedBytes(), 0) + for _, tc := range makeDefaultTestCases(ss) { + if tc.result != tc.expect { + fail(FailString("Default ScalarStuff: "+tc.what, tc.expect, tc.result)) + } + } + + // test assigned values + fbb.Reset() + ss = buildAssignedTable(fbb) + for _, tc := range makeAssignedTestCases(ss) { + if resolvePointer(tc.result) != tc.expect { + fail(FailString("Assigned ScalarStuff: "+tc.what, tc.expect, tc.result)) + } + } + + // test native object pack + fbb.Reset() + i8 := int8(5) + u8 := byte(6) + i16 := int16(7) + u16 := uint16(8) + i32 := int32(9) + u32 := uint32(10) + i64 := int64(11) + u64 := uint64(12) + f32 := float32(13) + f64 := float64(14) + b := true + enum := optional_scalars.OptionalByteTwo + obj := optional_scalars.ScalarStuffT{ + JustI8: 5, + MaybeI8: &i8, + DefaultI8: 5, + JustU8: 6, + MaybeU8: &u8, + DefaultU8: 6, + JustI16: 7, + MaybeI16: &i16, + DefaultI16: 7, + JustU16: 8, + MaybeU16: &u16, + DefaultU16: 8, + JustI32: 9, + MaybeI32: &i32, + DefaultI32: 9, + JustU32: 10, + MaybeU32: &u32, + DefaultU32: 10, + JustI64: 11, + MaybeI64: &i64, + DefaultI64: 11, + JustU64: 12, + MaybeU64: &u64, + DefaultU64: 12, + JustF32: 13, + MaybeF32: &f32, + DefaultF32: 13, + JustF64: 14, + MaybeF64: &f64, + DefaultF64: 14, + JustBool: true, + MaybeBool: &b, + DefaultBool: false, + JustEnum: optional_scalars.OptionalByteTwo, + MaybeEnum: &enum, + DefaultEnum: optional_scalars.OptionalByteTwo, + } + fbb.Finish(obj.Pack(fbb)) + ss = optional_scalars.GetRootAsScalarStuff(fbb.FinishedBytes(), 0) + for _, tc := range makeAssignedTestCases(ss) { + if resolvePointer(tc.result) != tc.expect { + fail(FailString("Native Object ScalarStuff: "+tc.what, tc.expect, tc.result)) + } + } + + // test native object unpack + fbb.Reset() + ss = buildAssignedTable(fbb) + ss.UnPackTo(&obj) + expectEq := func(what string, a, b interface{}) { + if resolvePointer(a) != b { + fail(FailString("Native Object Unpack ScalarStuff: "+what, b, a)) + } + } + expectEq("justI8", obj.JustI8, int8(5)) + expectEq("maybeI8", obj.MaybeI8, int8(5)) + expectEq("defaultI8", obj.DefaultI8, int8(5)) + expectEq("justU8", obj.JustU8, byte(6)) + expectEq("maybeU8", obj.MaybeU8, byte(6)) + expectEq("defaultU8", obj.DefaultU8, byte(6)) + expectEq("justI16", obj.JustI16, int16(7)) + expectEq("maybeI16", obj.MaybeI16, int16(7)) + expectEq("defaultI16", obj.DefaultI16, int16(7)) + expectEq("justU16", obj.JustU16, uint16(8)) + expectEq("maybeU16", obj.MaybeU16, uint16(8)) + expectEq("defaultU16", obj.DefaultU16, uint16(8)) + expectEq("justI32", obj.JustI32, int32(9)) + expectEq("maybeI32", obj.MaybeI32, int32(9)) + expectEq("defaultI32", obj.DefaultI32, int32(9)) + expectEq("justU32", obj.JustU32, uint32(10)) + expectEq("maybeU32", obj.MaybeU32, uint32(10)) + expectEq("defaultU32", obj.DefaultU32, uint32(10)) + expectEq("justI64", obj.JustI64, int64(11)) + expectEq("maybeI64", obj.MaybeI64, int64(11)) + expectEq("defaultI64", obj.DefaultI64, int64(11)) + expectEq("justU64", obj.JustU64, uint64(12)) + expectEq("maybeU64", obj.MaybeU64, uint64(12)) + expectEq("defaultU64", obj.DefaultU64, uint64(12)) + expectEq("justF32", obj.JustF32, float32(13)) + expectEq("maybeF32", obj.MaybeF32, float32(13)) + expectEq("defaultF32", obj.DefaultF32, float32(13)) + expectEq("justF64", obj.JustF64, float64(14)) + expectEq("maybeF64", obj.MaybeF64, float64(14)) + expectEq("defaultF64", obj.DefaultF64, float64(14)) + expectEq("justBool", obj.JustBool, true) + expectEq("maybeBool", obj.MaybeBool, true) + expectEq("defaultBool", obj.DefaultBool, false) + expectEq("justEnum", obj.JustEnum, optional_scalars.OptionalByteTwo) + expectEq("maybeEnum", obj.MaybeEnum, optional_scalars.OptionalByteTwo) + expectEq("defaultEnum", obj.DefaultEnum, optional_scalars.OptionalByteTwo) +} + +func CheckByKey(fail func(string, ...interface{})) { + expectEq := func(what string, a, b interface{}) { + if a != b { + fail(FailString("Lookup by key: "+what, b, a)) + } + } + + b := flatbuffers.NewBuilder(0) + name := b.CreateString("Boss") + + slime := &example.MonsterT{Name: "Slime"} + pig := &example.MonsterT{Name: "Pig"} + slimeBoss := &example.MonsterT{Name: "SlimeBoss"} + mushroom := &example.MonsterT{Name: "Mushroom"} + ironPig := &example.MonsterT{Name: "Iron Pig"} + + monsterOffsets := make([]flatbuffers.UOffsetT, 5) + monsterOffsets[0] = slime.Pack(b) + monsterOffsets[1] = pig.Pack(b) + monsterOffsets[2] = slimeBoss.Pack(b) + monsterOffsets[3] = mushroom.Pack(b) + monsterOffsets[4] = ironPig.Pack(b) + testarrayoftables := b.CreateVectorOfSortedTables(monsterOffsets, example.MonsterKeyCompare) + + str := &example.StatT{Id: "Strength", Count: 42} + luk := &example.StatT{Id: "Luck", Count: 51} + hp := &example.StatT{Id: "Health", Count: 12} + // Test default count value of 0 + mp := &example.StatT{Id: "Mana"} + + statOffsets := make([]flatbuffers.UOffsetT, 4) + statOffsets[0] = str.Pack(b) + statOffsets[1] = luk.Pack(b) + statOffsets[2] = hp.Pack(b) + statOffsets[3] = mp.Pack(b) + scalarKeySortedTablesOffset := b.CreateVectorOfSortedTables(statOffsets, example.StatKeyCompare) + + example.MonsterStart(b) + example.MonsterAddname(b, name) + example.MonsterAddtestarrayoftables(b, testarrayoftables) + example.MonsterAddscalar_key_sorted_tables(b, scalarKeySortedTablesOffset) + moff := example.MonsterEnd(b) + b.Finish(moff) + + monster := example.GetRootAsMonster(b.Bytes, b.Head()) + slimeMon := &example.Monster{} + monster.TestarrayoftablesByKey(slimeMon, slime.Name) + mushroomMon := &example.Monster{} + monster.TestarrayoftablesByKey(mushroomMon, mushroom.Name) + slimeBossMon := &example.Monster{} + monster.TestarrayoftablesByKey(slimeBossMon, slimeBoss.Name) + + strStat := &example.Stat{} + monster.ScalarKeySortedTablesByKey(strStat, str.Count) + lukStat := &example.Stat{} + monster.ScalarKeySortedTablesByKey(lukStat, luk.Count) + mpStat := &example.Stat{} + monster.ScalarKeySortedTablesByKey(mpStat, mp.Count) + + expectEq("Boss name", string(monster.Name()), "Boss") + expectEq("Slime name", string(slimeMon.Name()), slime.Name) + expectEq("Mushroom name", string(mushroomMon.Name()), mushroom.Name) + expectEq("SlimeBoss name", string(slimeBossMon.Name()), slimeBoss.Name) + expectEq("Strength Id", string(strStat.Id()), str.Id) + expectEq("Strength Count", strStat.Count(), str.Count) + expectEq("Luck Id", string(lukStat.Id()), luk.Id) + expectEq("Luck Count", lukStat.Count(), luk.Count) + expectEq("Mana Id", string(mpStat.Id()), mp.Id) + // Use default count value as key + expectEq("Mana Count", mpStat.Count(), uint16(0)) +} + +// BenchmarkVtableDeduplication measures the speed of vtable deduplication +// by creating prePop vtables, then populating b.N objects with a +// different single vtable. +// +// When b.N is large (as in long benchmarks), memory usage may be high. +func BenchmarkVtableDeduplication(b *testing.B) { + prePop := 10 + builder := flatbuffers.NewBuilder(0) + + // pre-populate some vtables: + for i := 0; i < prePop; i++ { + builder.StartObject(i) + for j := 0; j < i; j++ { + builder.PrependInt16Slot(j, int16(j), 0) + } + builder.EndObject() + } + + // benchmark deduplication of a new vtable: + b.ResetTimer() + for i := 0; i < b.N; i++ { + lim := prePop + + builder.StartObject(lim) + for j := 0; j < lim; j++ { + builder.PrependInt16Slot(j, int16(j), 0) + } + builder.EndObject() + } +} + +// BenchmarkParseGold measures the speed of parsing the 'gold' data +// used throughout this test suite. +func BenchmarkParseGold(b *testing.B) { + buf, offset := CheckGeneratedBuild(false, false, b.Fatalf) + monster := example.GetRootAsMonster(buf, offset) + + // use these to prevent allocations: + reuse_pos := example.Vec3{} + reuse_test3 := example.Test{} + reuse_table2 := flatbuffers.Table{} + reuse_monster2 := example.Monster{} + reuse_test4_0 := example.Test{} + reuse_test4_1 := example.Test{} + + b.SetBytes(int64(len(buf[offset:]))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + monster.Hp() + monster.Mana() + name := monster.Name() + _ = name[0] + _ = name[len(name)-1] + + monster.Pos(&reuse_pos) + reuse_pos.X() + reuse_pos.Y() + reuse_pos.Z() + reuse_pos.Test1() + reuse_pos.Test2() + reuse_pos.Test3(&reuse_test3) + reuse_test3.A() + reuse_test3.B() + monster.TestType() + monster.Test(&reuse_table2) + reuse_monster2.Init(reuse_table2.Bytes, reuse_table2.Pos) + name2 := reuse_monster2.Name() + _ = name2[0] + _ = name2[len(name2)-1] + monster.InventoryLength() + l := monster.InventoryLength() + for i := 0; i < l; i++ { + monster.Inventory(i) + } + monster.Test4Length() + monster.Test4(&reuse_test4_0, 0) + monster.Test4(&reuse_test4_1, 1) + + reuse_test4_0.A() + reuse_test4_0.B() + reuse_test4_1.A() + reuse_test4_1.B() + + monster.TestarrayofstringLength() + str0 := monster.Testarrayofstring(0) + _ = str0[0] + _ = str0[len(str0)-1] + str1 := monster.Testarrayofstring(1) + _ = str1[0] + _ = str1[len(str1)-1] + } +} + +// BenchmarkBuildGold uses generated code to build the example Monster. +func BenchmarkBuildGold(b *testing.B) { + buf, offset := CheckGeneratedBuild(false, false, b.Fatalf) + bytes_length := int64(len(buf[offset:])) + + reuse_str := "MyMonster" + reuse_test1 := "test1" + reuse_test2 := "test2" + reuse_fred := "Fred" + + b.SetBytes(bytes_length) + bldr := flatbuffers.NewBuilder(0) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bldr.Reset() + + str := bldr.CreateString(reuse_str) + test1 := bldr.CreateString(reuse_test1) + test2 := bldr.CreateString(reuse_test2) + fred := bldr.CreateString(reuse_fred) + + example.MonsterStartinventoryVector(bldr, 5) + bldr.PrependByte(4) + bldr.PrependByte(3) + bldr.PrependByte(2) + bldr.PrependByte(1) + bldr.PrependByte(0) + inv := bldr.EndVector(5) + + example.MonsterStart(bldr) + example.MonsterAddname(bldr, fred) + mon2 := example.MonsterEnd(bldr) + + example.MonsterStarttest4Vector(bldr, 2) + example.CreateTest(bldr, 10, 20) + example.CreateTest(bldr, 30, 40) + test4 := bldr.EndVector(2) + + example.MonsterStarttestarrayofstringVector(bldr, 2) + bldr.PrependUOffsetT(test2) + bldr.PrependUOffsetT(test1) + testArrayOfString := bldr.EndVector(2) + + example.MonsterStart(bldr) + + pos := example.CreateVec3(bldr, 1.0, 2.0, 3.0, 3.0, example.ColorGreen, 5, 6) + example.MonsterAddpos(bldr, pos) + + example.MonsterAddhp(bldr, 80) + example.MonsterAddname(bldr, str) + example.MonsterAddinventory(bldr, inv) + example.MonsterAddtest_type(bldr, 1) + example.MonsterAddtest(bldr, mon2) + example.MonsterAddtest4(bldr, test4) + example.MonsterAddtestarrayofstring(bldr, testArrayOfString) + mon := example.MonsterEnd(bldr) + + bldr.Finish(mon) + } +} diff --git a/tests/java_preserve_case/JavaPreserveCaseTest.java b/tests/java_preserve_case/JavaPreserveCaseTest.java new file mode 100644 index 00000000000..c70800cbde2 --- /dev/null +++ b/tests/java_preserve_case/JavaPreserveCaseTest.java @@ -0,0 +1,96 @@ +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.io.ByteStreams; +import com.google.flatbuffers.FlatBufferBuilder; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import org.junit.Test; +import preservecase.MyGame.Example.Any; +import preservecase.MyGame.Example.Monster; +import preservecase.MyGame.Example.Vec3; +import preservecase.optional_scalars.ScalarStuff; + +public class JavaPreserveCaseTest { + + private static ByteBuffer loadResource(String name) throws IOException { + try (InputStream is = JavaPreserveCaseTest.class.getClassLoader().getResourceAsStream(name)) { + if (is == null) { + throw new IOException("Resource not found: " + name); + } + return ByteBuffer.wrap(ByteStreams.toByteArray(is)); + } + } + + @Test + public void readExistingMonsterBuffer() throws IOException { + ByteBuffer bb = loadResource("monsterdata_test.mon"); + Monster monster = Monster.getRootAsMonster(bb); + + assertThat(monster.test_type()).isEqualTo((byte) Any.Monster); + assertThat(monster.testhashu32_fnv1()).isEqualTo(3715746113L); + assertThat(monster.testhashs32_fnv1()).isEqualTo(-579221183); + + Vec3 pos = monster.pos(); + assertThat(pos.test1()).isWithin(0.0001).of(3.0); + preservecase.MyGame.Example.Test firstTest = monster.test4(0); + assertThat(firstTest.a()).isEqualTo((short) 10); + } + + @Test + public void buildAndReadSnakeCaseFields() { + FlatBufferBuilder builder = new FlatBufferBuilder(); + int nameOffset = builder.createString("PreserveCase"); + Monster.startMonster(builder); + Monster.addName(builder, nameOffset); + Monster.addHp(builder, (short) 42); + Monster.addTestType(builder, Any.Monster); + int monsterOffset = Monster.endMonster(builder); + Monster.finishMonsterBuffer(builder, monsterOffset); + + ByteBuffer bb = builder.dataBuffer(); + Monster monster = Monster.getRootAsMonster(bb); + + assertThat(monster.name()).isEqualTo("PreserveCase"); + assertThat(monster.hp()).isEqualTo((short) 42); + assertThat(monster.test_type()).isEqualTo((byte) Any.Monster); + } + + @Test + public void optionalScalarsSnakeCaseAccessors() { + FlatBufferBuilder builder = new FlatBufferBuilder(); + ScalarStuff.startScalarStuff(builder); + ScalarStuff.addJustI8(builder, (byte) 7); + ScalarStuff.addMaybeI8(builder, (byte) 9); + ScalarStuff.addJustBool(builder, true); + ScalarStuff.addMaybeBool(builder, false); + ScalarStuff.addJustEnum(builder, (byte) 2); + ScalarStuff.addMaybeEnum(builder, (byte) 3); + ScalarStuff.addJustU8(builder, 200); + ScalarStuff.addMaybeU8(builder, 201); + ScalarStuff.addJustI16(builder, (short) 3000); + ScalarStuff.addMaybeI16(builder, (short) -123); + ScalarStuff.addJustU16(builder, 65000); + ScalarStuff.addMaybeU16(builder, 1234); + ScalarStuff.addJustU32(builder, 0xFEDCBAABL); + ScalarStuff.addJustF32(builder, 0.5f); + ScalarStuff.addJustF64(builder, 123.25); + ScalarStuff.addJustU64(builder, 123456789L); + ScalarStuff.addMaybeU64(builder, 10L); + ScalarStuff.addMaybeI8(builder, (byte) 9); + int scalarStuffOffset = ScalarStuff.endScalarStuff(builder); + builder.finish(scalarStuffOffset); + + ScalarStuff scalarStuff = ScalarStuff.getRootAsScalarStuff(builder.dataBuffer()); + assertThat(scalarStuff.just_i8()).isEqualTo((byte) 7); + assertThat(scalarStuff.maybe_i8()).isEqualTo((byte) 9); + assertThat(scalarStuff.just_bool()).isTrue(); + assertThat(scalarStuff.maybe_bool()).isFalse(); + assertThat(scalarStuff.just_enum()).isEqualTo((byte) 2); + assertThat(scalarStuff.maybe_enum()).isEqualTo((byte) 3); + assertThat(scalarStuff.just_u32()).isEqualTo(0xFEDCBAABL); + assertThat(scalarStuff.just_f32()).isWithin(0.0001f).of(0.5f); + assertThat(scalarStuff.just_f64()).isWithin(0.0001).of(123.25); + assertThat(scalarStuff.just_u64()).isEqualTo(123456789L); + } +} diff --git a/tests/phpTest.php b/tests/phpTest.php index a854e783e35..7c121fb646e 100644 --- a/tests/phpTest.php +++ b/tests/phpTest.php @@ -1,12 +1,31 @@ createString("MyMonster"); + $name = $fbb->createString('Fred'); + \MyGame\Example\Monster::startMonster($fbb); + \MyGame\Example\Monster::addname($fbb, $name); + $enemy = \MyGame\Example\Monster::endMonster($fbb); + + $inv = \MyGame\Example\Monster::createinventoryVector($fbb, array(0, 1, 2, 3, 4)); + + $fred = $fbb->createString('Fred'); + \MyGame\Example\Monster::startMonster($fbb); + \MyGame\Example\Monster::addname($fbb, $fred); + $mon2 = \MyGame\Example\Monster::endMonster($fbb); + + \MyGame\Example\Monster::starttest4Vector($fbb, 2); + \MyGame\Example\Test::createTest($fbb, 10, 20); + \MyGame\Example\Test::createTest($fbb, 30, 40); + $test4 = $fbb->endVector(); + + $testArrayOfString = \MyGame\Example\Monster::createtestarrayofstringVector($fbb, array( + $fbb->createString('test1'), + $fbb->createString('test2') + )); + + \MyGame\Example\Monster::startMonster($fbb); + \MyGame\Example\Monster::addpos($fbb, \MyGame\Example\Vec3::createVec3($fbb, + 1.0, 2.0, 3.0, //float + 3.0, // double + \MyGame\Example\Color::Green, + 5, //short + 6)); + \MyGame\Example\Monster::addhp($fbb, 80); + \MyGame\Example\Monster::addname($fbb, $str); + \MyGame\Example\Monster::addinventory($fbb, $inv); + \MyGame\Example\Monster::addtest_type($fbb, \MyGame\Example\Any::Monster); + \MyGame\Example\Monster::addtest($fbb, $mon2); + \MyGame\Example\Monster::addtest4($fbb, $test4); + \MyGame\Example\Monster::addtestarrayofstring($fbb, $testArrayOfString); + \MyGame\Example\Monster::addenemy($fbb, $enemy); + \MyGame\Example\Monster::addtestbool($fbb, true); + $mon = \MyGame\Example\Monster::endMonster($fbb); + + \MyGame\Example\Monster::finishMonsterBuffer($fbb, $mon); + + // Test it: + test_buffer($assert, $fbb->dataBuffer()); + + testByteBuffer($assert); + fuzzTest1($assert); +// testUnicode($assert); + + echo 'FlatBuffers php test: completed successfully' . PHP_EOL; +} + +try { + main(); + exit(0); +} catch(Exception $e) { + printf("Fatal error: Uncaught exception '%s' with message '%s. in %s:%d\n", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); + printf("Stack trace:\n"); + echo $e->getTraceAsString() . PHP_EOL; + printf(" thrown in in %s:%d\n", $e->getFile(), $e->getLine()); + + die(-1); +} + +function test_buffer(Assert $assert, Google\FlatBuffers\ByteBuffer $bb) { + + $assert->ok(MyGame\Example\Monster::MonsterBufferHasIdentifier($bb)); + $monster = \MyGame\Example\Monster::getRootAsMonster($bb); + + $assert->strictEqual($monster->gethp(), 80); + $assert->strictEqual($monster->getmana(), 150); // default + + $assert->strictEqual($monster->getname(), 'MyMonster'); + + $pos = $monster->getpos(); + $assert->strictEqual($pos->Getx(), 1.0); + $assert->strictEqual($pos->Gety(), 2.0); + $assert->strictEqual($pos->Getz(), 3.0); + + $assert->Equal($pos->Gettest1(), 3.0); + $assert->strictEqual($pos->Gettest2(), \MyGame\Example\Color::Green); + + $t = $pos->gettest3(); + $assert->strictEqual($t->Geta(), 5); + $assert->strictEqual($t->Getb(), 6); + $assert->strictEqual($monster->gettest_type(), \MyGame\Example\Any::Monster); + + $monster2 = new \MyGame\Example\Monster(); + $assert->strictEqual($monster->gettest($monster2) != null, true); + $assert->strictEqual($monster2->getname(), 'Fred'); + + $assert->strictEqual($monster->getinventoryLength(), 5); + $invsum = 0; + for ($i = 0; $i < $monster->getinventoryLength(); $i++) { + $invsum += $monster->getinventory($i); + } + $assert->strictEqual($invsum, 10); + + $assert->strictEqual(bin2hex($monster->getinventoryBytes()), "0001020304"); + + $test_0 = $monster->gettest4(0); + $test_1 = $monster->gettest4(1); + $assert->strictEqual($monster->gettest4Length(), 2); + $assert->strictEqual($test_0->Geta() + $test_0->Getb() + $test_1->Geta() + $test_1->Getb(), 100); + + $assert->strictEqual($monster->gettestarrayofstringLength(), 2); + $assert->strictEqual($monster->gettestarrayofstring(0), 'test1'); + $assert->strictEqual($monster->gettestarrayofstring(1), 'test2'); + + $fred = $monster->getenemy(); + $assert->Equal('Fred', $fred->getname()); + + $assert->strictEqual($monster->gettestbool(), true); +} + +//function testUnicode(Assert $assert) { +// // missing unicode_test.mon, implemented later +// $correct = file_get_contents('unicode_test.mon'); +// $json = json_decode(file_get_contents('unicode_test.json')); +// +// // Test reading +// $bb = flatbuffers\ByteBuffer::Wrap($correct); +// $monster = \MyGame\Example\Monster::getRootAsMonster($bb); +// $assert->strictEqual($monster->getname(), $json["name"]); +// +// //$assert->deepEqual(new Buffer(monster.name(flatbuffers.Encoding.UTF8_BYTES)), new Buffer(json.name)); +// //assert.strictEqual(monster.testarrayoftablesLength(), json.testarrayoftables.length); +// foreach ($json["testarrayoftables"]as $i => $table) { +// $value = $monster->GetTestArrayOfTables($i); +// $assert->strictEqual($value->getname(), $table["name"]); +// //assert.deepEqual(new Buffer(value.name(flatbuffers.Encoding.UTF8_BYTES)), new Buffer(table.name)); +// } +// $assert->strictEqual($monster->gettestarrayofstringLength(), $json["testarrayofstring"]["length"]); +// foreach ($json["testarrayofstring"] as $i => $string) { +// $assert->strictEqual($monster->gettestarrayofstring($i), $string); +// //assert.deepEqual(new Buffer(monster.testarrayofstring(i, flatbuffers.Encoding.UTF8_BYTES)), new Buffer(string)); +// } +// +// // Test writing +// $fbb = new FlatBuffers\FlatBufferBuilder(1); +// $name = $fbb->CreateString($json["name"]); +// $testarrayoftablesOffsets = array_map(function($table) use($fbb) { +// $name = $fbb->CreateString($table["name"]); +// \MyGame\Example\Monster::startMonster($fbb); +// \MyGame\Example\Monster::addname($fbb, $name); +// return \MyGame\Example\Monster::endMonster($fbb); +// }, $json["testarrayoftables"]); +// $testarrayoftablesOffset = \MyGame\Example\Monster::createtestarrayoftablesVector($fbb, +// $testarrayoftablesOffsets); +//// $testarrayofstringOffset = \MyGame\Example\Monster::createtestarrayofstringVector($fbb, +//// $json["testarrayofstring"].map(function(string) { return fbb.createString(string); })); +// +// \MyGame\Example\Monster::startMonster($fbb); +// \MyGame\Example\Monster::addTestarrayofstring($fbb, $testarrayoftablesOffset); +// \MyGame\Example\Monster::addTestarrayoftables($fbb, $testarrayoftablesOffset); +// \MyGame\Example\Monster::addname($fbb, $name); +// \MyGame\Example\Monster::finishMonsterBuffer($fbb, \MyGame\Example\Monster::endMonster($fbb)); +// //;assert.deepEqual(new Buffer(fbb.asUint8Array()), correct); +//} + +// Low level stress/fuzz test: serialize/deserialize a variety of +// different kinds of data in different combinations +function fuzzTest1(Assert $assert) +{ + + // Values we're testing against: chosen to ensure no bits get chopped + // off anywhere, and also be different from eachother. + $bool_val = true; + $char_val = -127; // 0x81 + $uchar_val = 0xFF; + $short_val = -32222; // 0x8222; + $ushort_val = 0xFEEE; + $int_val = 0x7fffffff | 0; + // for now + $uint_val = 1; + $long_val = 2; + $ulong_val = 3; + +// var uint_val = 0xFDDDDDDD; +// var long_val = new flatbuffers.Long(0x44444444, 0x84444444); +// var ulong_val = new flatbuffers.Long(0xCCCCCCCC, 0xFCCCCCCC); + + $float_val = 3.14159; + $double_val = 3.14159265359; + + $test_values_max = 11; + $fields_per_object = 4; + // current implementation is not good at encoding. + $num_fuzz_objects = 1000; + $builder = new Google\FlatBuffers\FlatBufferBuilder(1); + + // can't use same implementation due to PHP_INTMAX overflow issue. + // we use mt_rand function to reproduce fuzzy test. + mt_srand(48271); + $objects = array(); + // Generate num_fuzz_objects random objects each consisting of + // fields_per_object fields, each of a random type. + for ($i = 0; $i < $num_fuzz_objects; $i++) { + $builder->startObject($fields_per_object); + for ($f = 0; $f < $fields_per_object; $f++) { + $choice = mt_rand() % $test_values_max; + switch ($choice) { + case 0: + $builder->addBoolX($f, $bool_val, 0); + break; + case 1: + $builder->addByteX($f, $char_val, 0); + break; + case 2: + $builder->addSbyteX($f, $uchar_val, 0); + break; + case 3: + $builder->addShortX($f, $short_val, 0); + break; + case 4: + $builder->addUshortX($f, $ushort_val, 0); + break; + case 5: + $builder->addIntX($f, $int_val, 0); + break; + case 6: + $builder->addUintX($f, $uint_val, 0); + break; + case 7: + $builder->addLongX($f, $long_val, 0); + break; + case 8: + $builder->addUlongX($f, $ulong_val, 0); + break; + case 9: + $builder->addFloatX($f, $float_val, 0); + break; + case 10: + $builder->addDoubleX($f, $double_val, 0); + break; + } + } + $objects[] = $builder->endObject(); + } + $builder->prep(8, 0); // Align whole buffer. + + mt_srand(48271); // Reset + $builder->finish($objects[count($objects) - 1]); + + $view = Google\FlatBuffers\ByteBuffer::wrap($builder->sizedByteArray()); + for ($i = 0; $i < $num_fuzz_objects; $i++) { + $offset = $view->capacity() - $objects[$i]; + for ($f = 0; $f < $fields_per_object; $f++) { + $choice = mt_rand() % $test_values_max; + $vtable_offset = fieldIndexToOffset($f); + $vtable = $offset - $view->getInt($offset); + $assert->ok($vtable_offset < $view->getShort($vtable)); + $field_offset = $offset + $view->getShort($vtable + $vtable_offset); + switch ($choice) { + case 0: + $assert->strictEqual(!!$view->getBool($field_offset), $bool_val); + break; + case 1: + $assert->strictEqual($view->getSbyte($field_offset), $char_val); + break; + case 2: + $assert->strictEqual($view->getByte($field_offset), $uchar_val); + break; + case 3: + $assert->strictEqual($view->getShort($field_offset), $short_val); + break; + case 4: + $assert->strictEqual($view->getUShort($field_offset), $ushort_val); + break; + case 5: + $assert->strictEqual($view->getInt($field_offset), $int_val); + break; + case 6: + $assert->strictEqual($view->getUint($field_offset), $uint_val); + break; + case 7: + if (PHP_INT_SIZE <= 4) break; + $assert->strictEqual($view->getLong($field_offset), $long_val); + break; + case 8: + if (PHP_INT_SIZE <= 4) break; + $assert->strictEqual($view->getUlong($field_offset), $ulong_val); + break; + case 9: + $assert->strictEqual(floor($view->getFloat($field_offset)), floor($float_val)); + break; + case 10: + $assert->strictEqual($view->getDouble($field_offset), $double_val); + break; + } + } + } +} + +function fieldIndexToOffset($field_id) { + // Should correspond to what EndTable() below builds up. + $fixed_fields = 2; // Vtable size and Object Size. + return ($field_id + $fixed_fields) * 2; +} + +function testByteBuffer(Assert $assert) { + + //Test: ByteBuffer_Length_MatchesBufferLength + $buffer = str_repeat("\0", 100); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal($uut->capacity(), strlen($buffer)); + + //Test: ByteBuffer_PutBytePopulatesBufferAtZeroOffset + $buffer = "\0"; + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $uut->putByte(0, "\x63"); // 99 + $assert->Equal("\x63", $uut->_buffer[0]); // don't share buffer as php user might confuse reference. + + //Test: ByteBuffer_PutByteCannotPutAtOffsetPastLength + $buffer = "\0"; + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putByte(1, "\x63"); // 99 + }); + + //Test: ByteBuffer_PutShortPopulatesBufferCorrectly + $buffer = str_repeat("\0", 2); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $uut->putShort(0, 1); + + // Ensure Endiannes was written correctly + $assert->Equal(chr(0x01), $uut->_buffer[0]); + $assert->Equal(chr(0x00), $uut->_buffer[1]); + + $buffer = str_repeat("\0", 2); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $uut->putShort(0, -32768); + + // Ensure Endiannes was written correctly + $assert->Equal(chr(0x00), $uut->_buffer[0]); + $assert->Equal(chr(0x80), $uut->_buffer[1]); + + //Test: ByteBuffer_PutShortCannotPutAtOffsetPastLength + $buffer = "\0"; + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putShort(2, 2); // 99 + }); + + //Test: ByteBuffer_PutShortChecksLength + $buffer = "\0"; + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putShort(0, 2); // 99 + }); + + //Test: ByteBuffer_PutShortChecksLengthAndOffset + $buffer = str_repeat("\0", 2); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putShort(1, 2); // 99 + }); + + //Test: ByteBuffer_PutIntPopulatesBufferCorrectly + $buffer = str_repeat("\0", 4); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $uut->putInt(0, 0x0A0B0C0D); + $assert->Equal(chr(0x0D), $uut->_buffer[0]); + $assert->Equal(chr(0x0C), $uut->_buffer[1]); + $assert->Equal(chr(0x0B), $uut->_buffer[2]); + $assert->Equal(chr(0x0A), $uut->_buffer[3]); + + $buffer = str_repeat("\0", 4); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $uut->putInt(0, -2147483648); + $assert->Equal(chr(0x00), $uut->_buffer[0]); + $assert->Equal(chr(0x00), $uut->_buffer[1]); + $assert->Equal(chr(0x00), $uut->_buffer[2]); + $assert->Equal(chr(0x80), $uut->_buffer[3]); + + //Test: ByteBuffer_PutIntCannotPutAtOffsetPastLength + $buffer = str_repeat("\0", 4); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putInt(2, 0x0A0B0C0D); + }); + + //Test: ByteBuffer_PutIntChecksLength + $buffer = str_repeat("\0", 1); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putInt(0, 0x0A0B0C0D); + }); + + //Test: ByteBuffer_PutIntChecksLengthAndOffset + $buffer = str_repeat("\0", 4); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putInt(2, 0x0A0B0C0D); + }); + + if (PHP_INT_SIZE > 4) { + //Test: ByteBuffer_PutLongPopulatesBufferCorrectly + $buffer = str_repeat("\0", 8); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $uut->putLong(0, 0x010203040A0B0C0D); + $assert->Equal(chr(0x0D), $uut->_buffer[0]); + $assert->Equal(chr(0x0C), $uut->_buffer[1]); + $assert->Equal(chr(0x0B), $uut->_buffer[2]); + $assert->Equal(chr(0x0A), $uut->_buffer[3]); + $assert->Equal(chr(0x04), $uut->_buffer[4]); + $assert->Equal(chr(0x03), $uut->_buffer[5]); + $assert->Equal(chr(0x02), $uut->_buffer[6]); + $assert->Equal(chr(0x01), $uut->_buffer[7]); + + //Test: ByteBuffer_PutLongCannotPutAtOffsetPastLength + $buffer = str_repeat("\0", 8); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putLong(2, 0x010203040A0B0C0D); + }); + + //Test: ByteBuffer_PutLongCannotPutAtOffsetPastLength + $buffer = str_repeat("\0", 1); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putLong(0, 0x010203040A0B0C0D); + }); + + + //Test: ByteBuffer_PutLongChecksLengthAndOffset + $buffer = str_repeat("\0", 8); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->putLong(2, 0x010203040A0B0C0D); + }); + } + + //Test: ByteBuffer_GetByteReturnsCorrectData + $buffer = str_repeat("\0", 1); + $buffer[0] = "\x63"; + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal("\x63", $uut->get(0)); + + //Test: ByteBuffer_GetByteChecksOffset + $buffer = str_repeat("\0", 1); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->get(1); + }); + + //Test: ByteBuffer_GetShortReturnsCorrectData + $buffer = str_repeat("\0", 2); + $buffer[0] = chr(0x01); + $buffer[1] = chr(0x00); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(1, $uut->getShort(0)); + + //Test: ByteBuffer_GetShortReturnsCorrectData (signed value) + $buffer = str_repeat("\0", 2); + $buffer[0] = chr(0x00); + $buffer[1] = chr(0x80); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(-32768, $uut->getShort(0)); + + //Test: ByteBuffer_GetShortChecksOffset + $buffer = str_repeat("\0", 2); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->getShort(2); + }); + + //Test: ByteBuffer_GetShortChecksLength + $buffer = str_repeat("\0", 2); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->getShort(1); + }); + + //Test: ByteBuffer_GetIntReturnsCorrectData + $buffer = str_repeat("\0", 4); + $buffer[0] = chr(0x0D); + $buffer[1] = chr(0x0C); + $buffer[2] = chr(0x0B); + $buffer[3] = chr(0x0A); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(0x0A0B0C0D, $uut->getInt(0)); + + $buffer = str_repeat("\0", 4); + $buffer[0] = chr(0x00); + $buffer[1] = chr(0x00); + $buffer[2] = chr(0x00); + $buffer[3] = chr(0x80); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(-2147483648, $uut->getInt(0)); + + //Test: ByteBuffer_GetIntChecksOffset + $buffer = str_repeat("\0", 4); + + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->getInt(4); + }); + + //Test: ByteBuffer_GetIntChecksLength + $buffer = str_repeat("\0", 2); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->getInt(0); + }); + + if (PHP_INT_SIZE > 4) { + //Test: ByteBuffer_GetLongReturnsCorrectData + $buffer = str_repeat("\0", 8); + $buffer[0] = chr(0x0D); + $buffer[1] = chr(0x0C); + $buffer[2] = chr(0x0B); + $buffer[3] = chr(0x0A); + $buffer[4] = chr(0x04); + $buffer[5] = chr(0x03); + $buffer[6] = chr(0x02); + $buffer[7] = chr(0x01); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(0x010203040A0B0C0D, $uut->getLong(0)); + + //Test: Signed Long + $buffer = str_repeat("\0", 8); + $buffer[0] = chr(0x00); + $buffer[1] = chr(0x00); + $buffer[2] = chr(0x00); + $buffer[3] = chr(0x00); + $buffer[4] = chr(0x00); + $buffer[5] = chr(0x00); + $buffer[6] = chr(0x00); + $buffer[7] = chr(0x80); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(-1 << 63, $uut->getLong(0)); + } + + //Test: ByteBuffer_GetLongChecksOffset + $buffer = str_repeat("\0", 8); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->getLong(8); + }); + + //Test: ByteBuffer_GetLongChecksLength + $buffer = str_repeat("\0", 7); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Throws(new OutOfRangeException(), function() use ($uut) { + $uut->getLong(0); + }); + + //Test: big endian + $buffer = str_repeat("\0", 2); + // 0xFF 0x00 + // Little Endian: 255 + // Big Endian: 65280 + $buffer[0] = chr(0xff); + $buffer[1] = chr(0x00); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(65280, $uut->readLittleEndian(0, 2, true)); + + $buffer = str_repeat("\0", 4); + $buffer[0] = chr(0x0D); + $buffer[1] = chr(0x0C); + $buffer[2] = chr(0x0B); + $buffer[3] = chr(0x0A); + $uut = Google\FlatBuffers\ByteBuffer::wrap($buffer); + $assert->Equal(0x0D0C0B0A, $uut->readLittleEndian(0, 4, true)); + +} + +class Assert { + public function ok($result, $message = "") { + if (!$result){ + throw new Exception(!empty($message) ? $message : "{$result} is not true."); + } + } + + public function Equal($result, $expected, $message = "") { + if ($result != $expected) { + throw new Exception(!empty($message) ? $message : "given the result {$result} is not equals as {$expected}"); + } + } + + + public function strictEqual($result, $expected, $message = "") { + if ($result !== $expected) { + throw new Exception(!empty($message) ? $message : "given the result {$result} is not strict equals as {$expected}"); + } + } + + public function Throws($class, Callable $callback) { + try { + $callback(); + + throw new \Exception("passed statement don't throw an exception."); + } catch (\Exception $e) { + if (get_class($e) != get_class($class)) { + throw new Exception("passed statement doesn't throw " . get_class($class) . ". throws " . get_class($e) . ": {$e->getMessage()}"); + } + } + } +} diff --git a/tests/phpUnionVectorTest.php b/tests/phpUnionVectorTest.php index 4b5e25885ee..4ad521253e5 100644 --- a/tests/phpUnionVectorTest.php +++ b/tests/phpUnionVectorTest.php @@ -1,15 +1,24 @@ |object $target + * @param array $candidates + * @param mixed ...$args + * @return mixed + */ +function callMethodWithFallback($target, array $candidates, ...$args) { + foreach ($candidates as $method) { + if (method_exists($target, $method)) { + if (is_string($target)) { + return $target::$method(...$args); + } + return $target->$method(...$args); + } + } + $type = is_string($target) ? $target : get_class($target); + throw new Exception("None of the expected methods exist on {$type}: " . implode(', ', $candidates)); +} + function main() { $assert = new Assert(); @@ -57,11 +86,13 @@ function main() ]; Attacker::startAttacker($fbb); - Attacker::addSwordAttackDamage($fbb, 5); + callMethodWithFallback('Attacker', ['addSwordAttackDamage', 'addsword_attack_damage'], $fbb, 5); $attackerOffset = Attacker::endAttacker($fbb); - $charTypesOffset = Movie::createCharactersTypeVector($fbb, $charTypes); - $charsOffset = Movie::createCharactersVector( + $charTypesOffset = callMethodWithFallback('Movie', ['createCharactersTypeVector', 'createcharacters_typeVector'], $fbb, $charTypes); + $charsOffset = callMethodWithFallback( + 'Movie', + ['createCharactersVector', 'createcharactersVector'], $fbb, [ BookReader::createBookReader($fbb, 7), @@ -71,29 +102,31 @@ function main() ); Movie::startMovie($fbb); - Movie::addCharactersType($fbb, $charTypesOffset); - Movie::addCharacters($fbb, $charsOffset); + callMethodWithFallback('Movie', ['addCharactersType', 'addcharacters_type'], $fbb, $charTypesOffset); + callMethodWithFallback('Movie', ['addCharacters', 'addcharacters'], $fbb, $charsOffset); Movie::finishMovieBuffer($fbb, Movie::endMovie($fbb)); $buf = Google\FlatBuffers\ByteBuffer::wrap($fbb->dataBuffer()->data()); $movie = Movie::getRootAsMovie($buf); - $assert->strictEqual($movie->getCharactersTypeLength(), count($charTypes)); - $assert->strictEqual($movie->getCharactersLength(), $movie->getCharactersTypeLength()); + $charactersTypeLength = callMethodWithFallback($movie, ['getCharactersTypeLength', 'getcharacters_typeLength']); + $charactersLength = callMethodWithFallback($movie, ['getCharactersLength', 'getcharactersLength']); + $assert->strictEqual($charactersTypeLength, count($charTypes)); + $assert->strictEqual($charactersLength, $charactersTypeLength); for ($i = 0; $i < count($charTypes); ++$i) { - $assert->strictEqual($movie->getCharactersType($i), $charTypes[$i]); + $assert->strictEqual(callMethodWithFallback($movie, ['getCharactersType', 'getcharacters_type'], $i), $charTypes[$i]); } - $bookReader7 = $movie->getCharacters(0, new BookReader()); - $assert->strictEqual($bookReader7->getBooksRead(), 7); + $bookReader7 = callMethodWithFallback($movie, ['getCharacters', 'getcharacters'], 0, new BookReader()); + $assert->strictEqual(callMethodWithFallback($bookReader7, ['getBooksRead', 'Getbooks_read']), 7); - $attacker = $movie->getCharacters(1, new Attacker()); - $assert->strictEqual($attacker->getSwordAttackDamage(), 5); + $attacker = callMethodWithFallback($movie, ['getCharacters', 'getcharacters'], 1, new Attacker()); + $assert->strictEqual(callMethodWithFallback($attacker, ['getSwordAttackDamage', 'getsword_attack_damage']), 5); - $bookReader2 = $movie->getCharacters(2, new BookReader()); - $assert->strictEqual($bookReader2->getBooksRead(), 2); + $bookReader2 = callMethodWithFallback($movie, ['getCharacters', 'getcharacters'], 2, new BookReader()); + $assert->strictEqual(callMethodWithFallback($bookReader2, ['getBooksRead', 'Getbooks_read']), 2); } try { diff --git a/tests/phpUnionVectorTest.sh b/tests/phpUnionVectorTest.sh index a6c3f264401..7b3fc36be39 100755 --- a/tests/phpUnionVectorTest.sh +++ b/tests/phpUnionVectorTest.sh @@ -1,8 +1,15 @@ #!/bin/bash -set -e +set -eu -../flatc --php -o php union_vector/union_vector.fbs -php phpUnionVectorTest.php +preserve_case_flag="--preserve-case" +output_dir="php_preserve_case" +echo "Running PHP union vector test with preserve-case enabled." + +rm -rf "${output_dir}" +mkdir -p "${output_dir}" + +../flatc --php ${preserve_case_flag} -o "${output_dir}" union_vector/union_vector.fbs +PHP_UNION_GENERATED_DIR="$(pwd)/${output_dir}" php phpUnionVectorTest.php echo 'PHP union vector test passed' diff --git a/tests/phpUnionVectorTestPreserveCase.php b/tests/phpUnionVectorTestPreserveCase.php new file mode 100644 index 00000000000..8d79c47a16d --- /dev/null +++ b/tests/phpUnionVectorTestPreserveCase.php @@ -0,0 +1,118 @@ +dataBuffer()->data()); + + $movie = Movie::getRootAsMovie($buf); + + $assert->strictEqual($movie->getcharacters_typeLength(), count($charTypes)); + $assert->strictEqual($movie->getcharactersLength(), $movie->getcharacters_typeLength()); + + for ($i = 0; $i < count($charTypes); ++$i) { + $assert->strictEqual($movie->getcharacters_type($i), $charTypes[$i]); + } + + $bookReader7 = $movie->getcharacters(0, new BookReader()); + $assert->strictEqual($bookReader7->Getbooks_read(), 7); + + $attacker = $movie->getcharacters(1, new Attacker()); + $assert->strictEqual($attacker->getsword_attack_damage(), 5); + + $bookReader2 = $movie->getcharacters(2, new BookReader()); + $assert->strictEqual($bookReader2->Getbooks_read(), 2); +} + +try { + main(); + exit(0); +} catch(Exception $e) { + printf("Fatal error: Uncaught exception '%s' with message '%s. in %s:%d\n", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); + printf("Stack trace:\n"); + echo $e->getTraceAsString() . PHP_EOL; + printf(" thrown in in %s:%d\n", $e->getFile(), $e->getLine()); + + die(-1); +} diff --git a/tests/py_test_preserve_case.py b/tests/py_test_preserve_case.py new file mode 100644 index 00000000000..4dfebb91be0 --- /dev/null +++ b/tests/py_test_preserve_case.py @@ -0,0 +1,3412 @@ +# coding=utf-8 +# Copyright 2014 Google Inc. All rights reserved. +# +# 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. + +import os +import os.path +import sys +import inspect +import types + +PY_VERSION = sys.version_info[:2] + +import ctypes +from collections import defaultdict +import math +import random +import timeit +import unittest + +from flatbuffers import compat +from flatbuffers import util +from flatbuffers.compat import range_func as compat_range +from flatbuffers.compat import NumpyRequiredForThisFeature + +import flatbuffers +from flatbuffers import number_types as N + +import MyGame # refers to generated code +import MyGame.Example # refers to generated code +import MyGame.Example.Any # refers to generated code +import MyGame.Example.Color # refers to generated code +import MyGame.Example.Monster # refers to generated code +import MyGame.Example.Test # refers to generated code +import MyGame.Example.Stat # refers to generated code +import MyGame.Example.Vec3 # refers to generated code +import MyGame.MonsterExtra # refers to generated code +import MyGame.InParentNamespace # refers to generated code +import MyGame.Example.ArrayTable # refers to generated code +import MyGame.Example.ArrayStruct # refers to generated code +import MyGame.Example.NestedStruct # refers to generated code +import MyGame.Example.TestEnum # refers to generated code +import MyGame.Example.NestedUnion.NestedUnionTest # refers to generated code +import MyGame.Example.NestedUnion.Vec3 # refers to generated code +import MyGame.Example.NestedUnion.Any # refers to generated code +import MyGame.Example.NestedUnion.Test # refers to generated code +import MyGame.Example.NestedUnion.Color # refers to generated code +import monster_test_generated # the one-file version +import optional_scalars +import optional_scalars.ScalarStuff + +_GENERATED_ROOT = os.path.dirname(os.path.abspath(__file__)) + + +def _normalize_name(name): + return name.replace('_', '').lower() + + +def _patch_class(cls): + """Installs case-preserving attribute fallbacks on generated classes.""" + if getattr(cls, '__preserve_case_patched__', False): + return + if not hasattr(cls, '__dict__'): + return + module = sys.modules.get(cls.__module__) + module_path = getattr(module, '__file__', None) + if not module_path or not module_path.startswith(_GENERATED_ROOT): + return + try: + cls.__preserve_case_patched__ = True + except (AttributeError, TypeError): + return + + members = dict(cls.__dict__) + normalized_names = {} + for attr_name, attr_value in members.items(): + if attr_name.startswith('__'): + continue + normalized_names.setdefault(_normalize_name(attr_name), attr_name) + if isinstance(attr_value, type): + _patch_class(attr_value) + + try: + signature = inspect.signature(cls.__init__) + except (TypeError, ValueError): + field_map = {} + else: + field_map = {} + for param in signature.parameters.values(): + if param.name == 'self': + continue + field_map.setdefault(_normalize_name(param.name), param.name) + + original_getattr = getattr(cls, '__getattr__', None) + + def _compat_getattr(self, name): + normalized = _normalize_name(name) + target_name = normalized_names.get(normalized) + if not target_name: + target_name = field_map.get(normalized) + if target_name: + return object.__getattribute__(self, target_name) + if original_getattr: + return original_getattr(self, name) + raise AttributeError(f"{cls.__name__!s} has no attribute {name!r}") + + cls.__getattr__ = _compat_getattr + + if field_map: + original_setattr = cls.__setattr__ + + def _compat_setattr(self, name, value): + target_name = field_map.get(_normalize_name(name), name) + return original_setattr(self, target_name, value) + + cls.__setattr__ = _compat_setattr + + +def _patch_module(module): + """Installs compatibility fallbacks on modules produced by flatc.""" + if getattr(module, '__preserve_case_patched__', False): + return + module.__preserve_case_patched__ = True + + members = dict(vars(module)) + normalized_names = {} + for attr_name, attr_value in members.items(): + if attr_name.startswith('__') or attr_name == '__builtins__': + continue + normalized_names.setdefault(_normalize_name(attr_name), attr_name) + if isinstance(attr_value, type): + _patch_class(attr_value) + + def _compat_getattr(name): + target_name = normalized_names.get(_normalize_name(name)) + if target_name: + value = getattr(module, target_name) + setattr(module, name, value) + return value + raise AttributeError(f"module {module.__name__!s} has no attribute {name!r}") + + module.__getattr__ = _compat_getattr + + +def _apply_preserve_case_aliases(): + """Ensures tests can reference legacy camelCase symbols.""" + for module in list(sys.modules.values()): + if not isinstance(module, types.ModuleType): + continue + module_path = getattr(module, '__file__', None) + if not module_path or not module_path.startswith(_GENERATED_ROOT): + continue + _patch_module(module) + + +def create_namespace_shortcut(is_onefile): + # Create shortcut from either the one-file format or the multi-file format + global _ANY + global _COLOR + global _MONSTER + global _TEST + global _STAT + global _VEC3 + global _IN_PARENT_NAMESPACE + if is_onefile: + print('Testing with the one-file generated code') + _ANY = monster_test_generated + _COLOR = monster_test_generated + _MONSTER = monster_test_generated + _TEST = monster_test_generated + _STAT = monster_test_generated + _VEC3 = monster_test_generated + _IN_PARENT_NAMESPACE = monster_test_generated + else: + print('Testing with multi-file generated code') + _ANY = MyGame.Example.Any + _COLOR = MyGame.Example.Color + _MONSTER = MyGame.Example.Monster + _TEST = MyGame.Example.Test + _STAT = MyGame.Example.Stat + _VEC3 = MyGame.Example.Vec3 + _IN_PARENT_NAMESPACE = MyGame.InParentNamespace + + _apply_preserve_case_aliases() + + +def assertRaises(test_case, fn, exception_class): + """Backwards-compatible assertion for exceptions raised.""" + + exc = None + try: + fn() + except Exception as e: + exc = e + test_case.assertTrue(exc is not None) + test_case.assertTrue(isinstance(exc, exception_class)) + + +def byte_swap_array(np_version, arr): + """Performs byte swapping on a NumPy array, adapting to different NumPy versions. + + Args: + np_version: Version of NumPu (np.__version__) + arr: The input NumPy array. + + Returns: + A new NumPy array with byte order swapped. + """ + numpy_version_tuple = tuple(map(int, np_version.split('.')[:3])) + min_version_for_new_method_tuple = (2, 0, 0) + + if numpy_version_tuple >= min_version_for_new_method_tuple: + # 'S' indicates swap byte order. + return arr.byteswap().view(arr.dtype.newbyteorder('S')) + else: + return arr.byteswap().newbyteorder() + + +class TestWireFormat(unittest.TestCase): + + def test_wire_format(self): + # Verify that using the generated Python code builds a buffer without + # returning errors, and is interpreted correctly, for size prefixed + # representation and regular: + for sizePrefix in [True, False]: + for file_identifier in [None, b'MONS']: + gen_buf, gen_off = make_monster_from_generated_code( + sizePrefix=sizePrefix, file_identifier=file_identifier + ) + CheckReadBuffer( + gen_buf, + gen_off, + sizePrefix=sizePrefix, + file_identifier=file_identifier, + ) + + # Verify that the canonical flatbuffer file is readable by the + # generated Python code. Note that context managers are not part of + # Python 2.5, so we use the simpler open/close methods here: + f = open('monsterdata_test.mon', 'rb') + canonicalWireData = f.read() + f.close() + CheckReadBuffer(bytearray(canonicalWireData), 0, file_identifier=b'MONS') + + # Write the generated buffer out to a file: + f = open('monsterdata_python_wire.mon', 'wb') + f.write(gen_buf[gen_off:]) + f.close() + + +class TestObjectBasedAPI(unittest.TestCase): + """Tests the generated object based API.""" + + def test_consistency_with_repeated_pack_and_unpack(self): + """Checks the serialization and deserialization between a buffer and + + its python object. It tests in the same way as the C++ object API test, + ObjectFlatBuffersTest in test.cpp. + """ + + buf, off = make_monster_from_generated_code() + + # Turns a buffer into Python object (T class). + monster1 = _MONSTER.Monster.GetRootAs(buf, off) + monsterT1 = _MONSTER.MonsterT.InitFromObj(monster1) + + for sizePrefix in [True, False]: + # Re-serialize the data into a buffer. + b1 = flatbuffers.Builder(0) + if sizePrefix: + b1.FinishSizePrefixed(monsterT1.Pack(b1)) + else: + b1.Finish(monsterT1.Pack(b1)) + CheckReadBuffer(b1.Bytes, b1.Head(), sizePrefix) + + # Deserializes the buffer into Python object again. + monster2 = _MONSTER.Monster.GetRootAs(b1.Bytes, b1.Head()) + # Re-serializes the data into a buffer for one more time. + monsterT2 = _MONSTER.MonsterT.InitFromObj(monster2) + for sizePrefix in [True, False]: + # Re-serializes the data into a buffer + b2 = flatbuffers.Builder(0) + if sizePrefix: + b2.FinishSizePrefixed(monsterT2.Pack(b2)) + else: + b2.Finish(monsterT2.Pack(b2)) + CheckReadBuffer(b2.Bytes, b2.Head(), sizePrefix) + + def test_default_values_with_pack_and_unpack(self): + """Serializes and deserializes between a buffer with default values (no + + specific values are filled when the buffer is created) and its python + object. + """ + # Creates a flatbuffer with default values. + b1 = flatbuffers.Builder(0) + _MONSTER.MonsterStart(b1) + gen_mon = _MONSTER.MonsterEnd(b1) + b1.Finish(gen_mon) + + # Converts the flatbuffer into the object class. + monster1 = _MONSTER.Monster.GetRootAs(b1.Bytes, b1.Head()) + monsterT1 = _MONSTER.MonsterT.InitFromObj(monster1) + + # Packs the object class into another flatbuffer. + b2 = flatbuffers.Builder(0) + b2.Finish(monsterT1.Pack(b2)) + monster2 = _MONSTER.Monster.GetRootAs(b2.Bytes, b2.Head()) + # Checks the default values. + self.assertTrue(monster2.pos() is None) + self.assertEqual(monster2.mana(), 150) + self.assertEqual(monster2.hp(), 100) + self.assertTrue(monster2.name() is None) + self.assertEqual(monster2.inventory(0), 0) + self.assertEqual(monster2.InventoryAsNumpy(), 0) + self.assertEqual(monster2.inventoryLength(), 0) + self.assertTrue(monster2.inventoryIsNone()) + self.assertEqual(monster2.color(), 8) + self.assertEqual(monster2.test_type(), 0) + self.assertTrue(monster2.test() is None) + self.assertTrue(monster2.test4(0) is None) + self.assertEqual(monster2.test4Length(), 0) + self.assertTrue(monster2.Test4IsNone()) + self.assertEqual(monster2.testarrayofstring(0), '') + self.assertEqual(monster2.testarrayofstringLength(), 0) + self.assertTrue(monster2.TestarrayofstringIsNone()) + self.assertTrue(monster2.testarrayoftables(0) is None) + self.assertEqual(monster2.testarrayoftablesLength(), 0) + self.assertTrue(monster2.TestarrayoftablesIsNone()) + self.assertTrue(monster2.Enemy() is None) + self.assertEqual(monster2.testnestedflatbuffer(0), 0) + self.assertEqual(monster2.TestnestedflatbufferAsNumpy(), 0) + self.assertEqual(monster2.testnestedflatbufferLength(), 0) + self.assertTrue(monster2.TestnestedflatbufferIsNone()) + self.assertTrue(monster2.Testempty() is None) + self.assertFalse(monster2.testbool()) + self.assertEqual(monster2.testhashs32_fnv1(), 0) + self.assertEqual(monster2.testhashu32_fnv1(), 0) + self.assertEqual(monster2.Testhashs64Fnv1(), 0) + self.assertEqual(monster2.Testhashu64Fnv1(), 0) + self.assertEqual(monster2.Testhashs32Fnv1a(), 0) + self.assertEqual(monster2.Testhashu32Fnv1a(), 0) + self.assertEqual(monster2.Testhashs64Fnv1a(), 0) + self.assertEqual(monster2.Testhashu64Fnv1a(), 0) + self.assertEqual(monster2.Testarrayofbools(0), 0) + self.assertEqual(monster2.TestarrayofboolsAsNumpy(), 0) + self.assertEqual(monster2.TestarrayofboolsLength(), 0) + self.assertTrue(monster2.TestarrayofboolsIsNone()) + self.assertEqual(monster2.Testf(), 3.14159) + self.assertEqual(monster2.Testf2(), 3.0) + self.assertEqual(monster2.Testf3(), 0.0) + self.assertEqual(monster2.Testarrayofstring2(0), '') + self.assertEqual(monster2.Testarrayofstring2Length(), 0) + self.assertTrue(monster2.Testarrayofstring2IsNone()) + self.assertTrue(monster2.Testarrayofsortedstruct(0) is None) + self.assertEqual(monster2.TestarrayofsortedstructLength(), 0) + self.assertTrue(monster2.TestarrayofsortedstructIsNone()) + self.assertEqual(monster2.Flex(0), 0) + self.assertEqual(monster2.FlexAsNumpy(), 0) + self.assertEqual(monster2.FlexLength(), 0) + self.assertTrue(monster2.FlexIsNone()) + self.assertTrue(monster2.Test5(0) is None) + self.assertEqual(monster2.Test5Length(), 0) + self.assertTrue(monster2.Test5IsNone()) + self.assertEqual(monster2.VectorOfLongs(0), 0) + self.assertEqual(monster2.VectorOfLongsAsNumpy(), 0) + self.assertEqual(monster2.VectorOfLongsLength(), 0) + self.assertTrue(monster2.VectorOfLongsIsNone()) + self.assertEqual(monster2.VectorOfDoubles(0), 0) + self.assertEqual(monster2.VectorOfDoublesAsNumpy(), 0) + self.assertEqual(monster2.VectorOfDoublesLength(), 0) + self.assertTrue(monster2.VectorOfDoublesIsNone()) + self.assertTrue(monster2.parent_namespace_test() is None) + self.assertTrue(monster2.VectorOfReferrables(0) is None) + self.assertEqual(monster2.VectorOfReferrablesLength(), 0) + self.assertTrue(monster2.VectorOfReferrablesIsNone()) + self.assertEqual(monster2.SingleWeakReference(), 0) + self.assertEqual(monster2.VectorOfWeakReferences(0), 0) + self.assertEqual(monster2.VectorOfWeakReferencesAsNumpy(), 0) + self.assertEqual(monster2.VectorOfWeakReferencesLength(), 0) + self.assertTrue(monster2.VectorOfWeakReferencesIsNone()) + self.assertTrue(monster2.VectorOfStrongReferrables(0) is None) + self.assertEqual(monster2.VectorOfStrongReferrablesLength(), 0) + self.assertTrue(monster2.VectorOfStrongReferrablesIsNone()) + self.assertEqual(monster2.CoOwningReference(), 0) + self.assertEqual(monster2.VectorOfCoOwningReferences(0), 0) + self.assertEqual(monster2.VectorOfCoOwningReferencesAsNumpy(), 0) + self.assertEqual(monster2.VectorOfCoOwningReferencesLength(), 0) + self.assertTrue(monster2.VectorOfCoOwningReferencesIsNone()) + self.assertEqual(monster2.NonOwningReference(), 0) + self.assertEqual(monster2.VectorOfNonOwningReferences(0), 0) + self.assertEqual(monster2.VectorOfNonOwningReferencesAsNumpy(), 0) + self.assertEqual(monster2.VectorOfNonOwningReferencesLength(), 0) + self.assertTrue(monster2.VectorOfNonOwningReferencesIsNone()) + self.assertEqual(monster2.AnyUniqueType(), 0) + self.assertTrue(monster2.AnyUnique() is None) + self.assertEqual(monster2.AnyAmbiguousType(), 0) + self.assertTrue(monster2.AnyAmbiguous() is None) + self.assertEqual(monster2.VectorOfEnums(0), 0) + self.assertEqual(monster2.VectorOfEnumsAsNumpy(), 0) + self.assertEqual(monster2.VectorOfEnumsLength(), 0) + self.assertTrue(monster2.VectorOfEnumsIsNone()) + + def test_optional_scalars_with_pack_and_unpack(self): + """Serializes and deserializes between a buffer with optional values (no + + specific values are filled when the buffer is created) and its python + object. + """ + # Creates a flatbuffer with optional values. + b1 = flatbuffers.Builder(0) + optional_scalars.ScalarStuff.ScalarStuffStart(b1) + gen_opt = optional_scalars.ScalarStuff.ScalarStuffEnd(b1) + b1.Finish(gen_opt) + + # Converts the flatbuffer into the object class. + opts1 = optional_scalars.ScalarStuff.ScalarStuff.GetRootAs( + b1.Bytes, b1.Head() + ) + optsT1 = optional_scalars.ScalarStuff.ScalarStuffT.InitFromObj(opts1) + + # Packs the object class into another flatbuffer. + b2 = flatbuffers.Builder(0) + b2.Finish(optsT1.Pack(b2)) + opts2 = optional_scalars.ScalarStuff.ScalarStuff.GetRootAs( + b2.Bytes, b2.Head() + ) + optsT2 = optional_scalars.ScalarStuff.ScalarStuffT.InitFromObj(opts2) + # Checks the default values. + self.assertTrue(opts2.JustI8() == 0) + self.assertTrue(opts2.MaybeF32() is None) + self.assertTrue(opts2.DefaultBool() is True) + self.assertTrue(optsT2.justU16 == 0) + self.assertTrue(optsT2.maybeEnum is None) + self.assertTrue(optsT2.defaultU64 == 42) + + +class TestAllMutableCodePathsOfExampleSchema(unittest.TestCase): + """Tests the object API generated for monster_test.fbs for mutation + + purposes. In each test, the default values will be changed through the + object API. We'll then pack the object class into the buf class and read + the updated values out from it to validate if the values are mutated as + expected. + """ + + def setUp(self, *args, **kwargs): + super(TestAllMutableCodePathsOfExampleSchema, self).setUp(*args, **kwargs) + # Creates an empty monster flatbuffer, and loads it into the object + # class for future tests. + b = flatbuffers.Builder(0) + _MONSTER.MonsterStart(b) + self.monsterT = self._create_and_load_object_class(b) + + def _pack_and_load_buf_class(self, monsterT): + """Packs the object class into a flatbuffer and loads it into a buf + + class. + """ + b = flatbuffers.Builder(0) + b.Finish(monsterT.Pack(b)) + monster = _MONSTER.Monster.GetRootAs(b.Bytes, b.Head()) + return monster + + def _create_and_load_object_class(self, b): + """Finishs the creation of a monster flatbuffer and loads it into an + + object class. + """ + gen_mon = _MONSTER.MonsterEnd(b) + b.Finish(gen_mon) + monster = _MONSTER.Monster.GetRootAs(b.Bytes, b.Head()) + monsterT = _MONSTER.MonsterT() + monsterT.InitFromObj(monster) + return monsterT + + def test_mutate_pos(self): + posT = _VEC3.Vec3T() + posT.x = 4.0 + posT.y = 5.0 + posT.z = 6.0 + posT.test1 = 6.0 + posT.test2 = 7 + test3T = _TEST.TestT() + test3T.a = 8 + test3T.b = 9 + posT.test3 = test3T + self.monsterT.pos = posT + + # Packs the updated values. + monster = self._pack_and_load_buf_class(self.monsterT) + + # Checks if values are loaded correctly into the object class. + pos = monster.pos() + + # Verifies the properties of the Vec3. + self.assertEqual(pos.X(), 4.0) + self.assertEqual(pos.Y(), 5.0) + self.assertEqual(pos.Z(), 6.0) + self.assertEqual(pos.Test1(), 6.0) + self.assertEqual(pos.Test2(), 7) + t3 = _TEST.test() + t3 = pos.Test3(t3) + self.assertEqual(t3.a(), 8) + self.assertEqual(t3.B(), 9) + + def test_mutate_mana(self): + self.monsterT.mana = 200 + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.mana(), 200) + + def test_mutate_hp(self): + self.monsterT.hp = 200 + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.hp(), 200) + + def test_mutate_name(self): + self.monsterT.name = 'MyMonster' + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.name(), b'MyMonster') + + def test_mutate_inventory(self): + self.monsterT.inventory = [1, 7, 8] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.inventory(0), 1) + self.assertEqual(monster.inventory(1), 7) + self.assertEqual(monster.inventory(2), 8) + + def test_empty_inventory(self): + self.monsterT.inventory = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.inventoryIsNone()) + + def test_mutate_color(self): + self.monsterT.color = _COLOR.Color.Red + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.color(), _COLOR.Color.Red) + + def test_mutate_testtype(self): + self.monsterT.test_type = _ANY.Any.Monster + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.test_type(), _ANY.Any.Monster) + + def test_mutate_test(self): + testT = _MONSTER.MonsterT() + testT.hp = 200 + self.monsterT.test = testT + monster = self._pack_and_load_buf_class(self.monsterT) + # Initializes a Table from a union field Test(...). + table = monster.test() + + # Initializes a Monster from the Table from the union. + test_monster = _MONSTER.Monster() + test_monster.Init(table.Bytes, table.Pos) + self.assertEqual(test_monster.hp(), 200) + + def test_mutate_test4(self): + test0T = _TEST.TestT() + test0T.a = 10 + test0T.b = 20 + test1T = _TEST.TestT() + test1T.a = 30 + test1T.b = 40 + self.monsterT.test4 = [test0T, test1T] + + monster = self._pack_and_load_buf_class(self.monsterT) + test0 = monster.test4(0) + self.assertEqual(test0.a(), 10) + self.assertEqual(test0.B(), 20) + test1 = monster.test4(1) + self.assertEqual(test1.a(), 30) + self.assertEqual(test1.B(), 40) + + def test_empty_test4(self): + self.monsterT.test4 = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.Test4IsNone()) + + def test_mutate_testarrayofstring(self): + self.monsterT.testarrayofstring = [] + self.monsterT.testarrayofstring.append('test1') + self.monsterT.testarrayofstring.append('test2') + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.testarrayofstring(0), b'test1') + self.assertEqual(monster.testarrayofstring(1), b'test2') + + def test_empty_testarrayofstring(self): + self.monsterT.testarrayofstring = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.TestarrayofstringIsNone()) + + def test_mutate_testarrayoftables(self): + monsterT0 = _MONSTER.MonsterT() + monsterT0.hp = 200 + monsterT1 = _MONSTER.MonsterT() + monsterT1.hp = 400 + self.monsterT.testarrayoftables = [] + self.monsterT.testarrayoftables.append(monsterT0) + self.monsterT.testarrayoftables.append(monsterT1) + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.testarrayoftables(0).hp(), 200) + self.assertEqual(monster.testarrayoftables(1).hp(), 400) + + def test_empty_testarrayoftables(self): + self.monsterT.testarrayoftables = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.TestarrayoftablesIsNone()) + + def test_mutate_enemy(self): + monsterT = _MONSTER.MonsterT() + monsterT.hp = 200 + self.monsterT.enemy = monsterT + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.Enemy().hp(), 200) + + def test_mutate_testnestedflatbuffer(self): + self.monsterT.testnestedflatbuffer = [8, 2, 4] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.testnestedflatbuffer(0), 8) + self.assertEqual(monster.testnestedflatbuffer(1), 2) + self.assertEqual(monster.testnestedflatbuffer(2), 4) + + def test_empty_testnestedflatbuffer(self): + self.monsterT.testnestedflatbuffer = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.TestnestedflatbufferIsNone()) + + def test_mutate_testbool(self): + self.monsterT.testbool = True + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertTrue(monster.testbool()) + + def test_mutate_testhashes(self): + self.monsterT.testhashs32Fnv1 = 1 + self.monsterT.testhashu32Fnv1 = 2 + self.monsterT.testhashs64Fnv1 = 3 + self.monsterT.testhashu64Fnv1 = 4 + self.monsterT.testhashs32Fnv1a = 5 + self.monsterT.testhashu32Fnv1a = 6 + self.monsterT.testhashs64Fnv1a = 7 + self.monsterT.testhashu64Fnv1a = 8 + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.testhashs32_fnv1(), 1) + self.assertEqual(monster.testhashu32_fnv1(), 2) + self.assertEqual(monster.Testhashs64Fnv1(), 3) + self.assertEqual(monster.Testhashu64Fnv1(), 4) + self.assertEqual(monster.Testhashs32Fnv1a(), 5) + self.assertEqual(monster.Testhashu32Fnv1a(), 6) + self.assertEqual(monster.Testhashs64Fnv1a(), 7) + self.assertEqual(monster.Testhashu64Fnv1a(), 8) + + def test_mutate_testarrayofbools(self): + self.monsterT.testarrayofbools = [] + self.monsterT.testarrayofbools.append(True) + self.monsterT.testarrayofbools.append(True) + self.monsterT.testarrayofbools.append(False) + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.Testarrayofbools(0), True) + self.assertEqual(monster.Testarrayofbools(1), True) + self.assertEqual(monster.Testarrayofbools(2), False) + + def test_empty_testarrayofbools(self): + self.monsterT.testarrayofbools = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.TestarrayofboolsIsNone()) + + def test_mutate_testf(self): + self.monsterT.testf = 2.0 + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.Testf(), 2.0) + + def test_mutate_vectoroflongs(self): + self.monsterT.vectorOfLongs = [] + self.monsterT.vectorOfLongs.append(1) + self.monsterT.vectorOfLongs.append(100) + self.monsterT.vectorOfLongs.append(10000) + self.monsterT.vectorOfLongs.append(1000000) + self.monsterT.vectorOfLongs.append(100000000) + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.VectorOfLongs(0), 1) + self.assertEqual(monster.VectorOfLongs(1), 100) + self.assertEqual(monster.VectorOfLongs(2), 10000) + self.assertEqual(monster.VectorOfLongs(3), 1000000) + self.assertEqual(monster.VectorOfLongs(4), 100000000) + + def test_empty_vectoroflongs(self): + self.monsterT.vectorOfLongs = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.VectorOfLongsIsNone()) + + def test_mutate_vectorofdoubles(self): + self.monsterT.vectorOfDoubles = [] + self.monsterT.vectorOfDoubles.append(-1.7976931348623157e308) + self.monsterT.vectorOfDoubles.append(0) + self.monsterT.vectorOfDoubles.append(1.7976931348623157e308) + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.VectorOfDoubles(0), -1.7976931348623157e308) + self.assertEqual(monster.VectorOfDoubles(1), 0) + self.assertEqual(monster.VectorOfDoubles(2), 1.7976931348623157e308) + + def test_empty_vectorofdoubles(self): + self.monsterT.vectorOfDoubles = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.VectorOfDoublesIsNone()) + + def test_mutate_parentnamespacetest(self): + self.monsterT.parentNamespaceTest = ( + _IN_PARENT_NAMESPACE.InParentNamespaceT() + ) + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertTrue( + isinstance( + monster.parent_namespace_test(), + _IN_PARENT_NAMESPACE.InParentNamespace, + ) + ) + + def test_mutate_vectorofEnums(self): + self.monsterT.vectorOfEnums = [] + self.monsterT.vectorOfEnums.append(_COLOR.Color.Red) + self.monsterT.vectorOfEnums.append(_COLOR.Color.Blue) + self.monsterT.vectorOfEnums.append(_COLOR.Color.Red) + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertEqual(monster.VectorOfEnums(0), _COLOR.Color.Red) + self.assertEqual(monster.VectorOfEnums(1), _COLOR.Color.Blue) + self.assertEqual(monster.VectorOfEnums(2), _COLOR.Color.Red) + + def test_empty_vectorofEnums(self): + self.monsterT.vectorOfEnums = [] + monster = self._pack_and_load_buf_class(self.monsterT) + self.assertFalse(monster.VectorOfEnumsIsNone()) + + +def CheckReadBuffer(buf, offset, sizePrefix=False, file_identifier=None): + """CheckReadBuffer checks that the given buffer is evaluated correctly + + as the example Monster. + """ + + def asserter(stmt): + """An assertion helper that is separated from TestCase classes.""" + if not stmt: + raise AssertionError('CheckReadBuffer case failed') + + if file_identifier: + # test prior to removal of size_prefix + asserter( + util.GetBufferIdentifier(buf, offset, size_prefixed=sizePrefix) + == file_identifier + ) + asserter( + util.BufferHasIdentifier( + buf, + offset, + file_identifier=file_identifier, + size_prefixed=sizePrefix, + ) + ) + asserter( + _MONSTER.Monster.MonsterBufferHasIdentifier( + buf, offset, size_prefixed=sizePrefix + ) + ) + if sizePrefix: + size = util.GetSizePrefix(buf, offset) + asserter(size == len(buf[offset:]) - 4) + buf, offset = util.RemoveSizePrefix(buf, offset) + if file_identifier: + asserter(_MONSTER.Monster.MonsterBufferHasIdentifier(buf, offset)) + else: + asserter(not _MONSTER.Monster.MonsterBufferHasIdentifier(buf, offset)) + monster = _MONSTER.Monster.GetRootAs(buf, offset) + + asserter(monster.hp() == 80) + asserter(monster.mana() == 150) + asserter(monster.name() == b'MyMonster') + + # initialize a Vec3 from Pos() + vec = monster.pos() + asserter(vec is not None) + + # verify the properties of the Vec3 + asserter(vec.X() == 1.0) + asserter(vec.Y() == 2.0) + asserter(vec.Z() == 3.0) + asserter(vec.Test1() == 3.0) + asserter(vec.Test2() == 2) + + # initialize a Test from Test3(...) + t = _TEST.test() + t = vec.Test3(t) + asserter(t is not None) + + # verify the properties of the Test + asserter(t.a() == 5) + asserter(t.B() == 6) + + # verify that the enum code matches the enum declaration: + union_type = _ANY.Any + asserter(monster.test_type() == union_type.Monster) + + # initialize a Table from a union field Test(...) + table2 = monster.test() + asserter(type(table2) is flatbuffers.table.Table) + + # initialize a Monster from the Table from the union + monster2 = _MONSTER.Monster() + monster2.Init(table2.Bytes, table2.Pos) + + asserter(monster2.name() == b'Fred') + + # iterate through the first monster's inventory: + asserter(monster.inventoryLength() == 5) + asserter(not monster.inventoryIsNone()) + + invsum = 0 + for i in compat_range(monster.inventoryLength()): + v = monster.inventory(i) + invsum += int(v) + asserter(invsum == 10) + + for i in range(5): + asserter(monster.VectorOfLongs(i) == 10 ** (i * 2)) + + asserter(not monster.VectorOfDoublesIsNone()) + asserter(( + [-1.7976931348623157e308, 0, 1.7976931348623157e308] + == [ + monster.VectorOfDoubles(i) + for i in range(monster.VectorOfDoublesLength()) + ] + )) + + try: + # if numpy exists, then we should be able to get the + # vector as a numpy array + import numpy as np + + asserter(monster.InventoryAsNumpy().sum() == 10) + asserter(monster.InventoryAsNumpy().dtype == np.dtype(' 0: + self.assertEqual(init_buf, buf) + self.assertEqual(init_off, off) + else: + init_buf = buf + init_off = off + + +def CheckAgainstGoldDataGo(): + try: + gen_buf, gen_off = make_monster_from_generated_code() + fn = 'monsterdata_go_wire.mon' + if not os.path.exists(fn): + print('Go-generated data does not exist, failed.') + return False + + # would like to use a context manager here, but it's less + # backwards-compatible: + f = open(fn, 'rb') + go_wire_data = f.read() + f.close() + + CheckReadBuffer(bytearray(go_wire_data), 0) + if not bytearray(gen_buf[gen_off:]) == bytearray(go_wire_data): + raise AssertionError('CheckAgainstGoldDataGo failed') + except: + print('Failed to test against Go-generated test data.') + return False + + print( + 'Can read Go-generated test data, and Python generates bytewise identical' + ' data.' + ) + return True + + +def CheckAgainstGoldDataJava(): + try: + gen_buf, gen_off = make_monster_from_generated_code() + fn = 'monsterdata_java_wire.mon' + if not os.path.exists(fn): + print('Java-generated data does not exist, failed.') + return False + f = open(fn, 'rb') + java_wire_data = f.read() + f.close() + + CheckReadBuffer(bytearray(java_wire_data), 0) + except: + print('Failed to read Java-generated test data.') + return False + + print('Can read Java-generated test data.') + return True + + +class LCG(object): + """Include simple random number generator to ensure results will be the + + same cross platform. + http://en.wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator + """ + + __slots__ = ['n'] + + InitialLCGSeed = 48271 + + def __init__(self): + self.n = self.InitialLCGSeed + + def Reset(self): + self.n = self.InitialLCGSeed + + def Next(self): + self.n = ((self.n * 279470273) % 4294967291) & 0xFFFFFFFF + return self.n + + +def BenchmarkVtableDeduplication(count): + """BenchmarkVtableDeduplication measures the speed of vtable deduplication + + by creating `prePop` vtables, then populating `count` objects with a + different single vtable. + + When count is large (as in long benchmarks), memory usage may be high. + """ + + for prePop in (1, 10, 100, 1000): + builder = flatbuffers.Builder(0) + n = 1 + int(math.log(prePop, 1.5)) + + # generate some layouts: + layouts = set() + r = list(compat_range(n)) + while len(layouts) < prePop: + layouts.add(tuple(sorted(random.sample(r, int(max(1, n / 2)))))) + + layouts = list(layouts) + + # pre-populate vtables: + for layout in layouts: + builder.StartObject(n) + for j in layout: + builder.PrependInt16Slot(j, j, 0) + builder.EndObject() + + # benchmark deduplication of a new vtable: + def f(): + layout = random.choice(layouts) + builder.StartObject(n) + for j in layout: + builder.PrependInt16Slot(j, j, 0) + builder.EndObject() + + duration = timeit.timeit(stmt=f, number=count) + rate = float(count) / duration + print(( + 'vtable deduplication rate (n=%d, vtables=%d): %.2f sec' + % (prePop, len(builder.vtables), rate) + )) + + +def BenchmarkCheckReadBuffer(count, buf, off): + """BenchmarkCheckReadBuffer measures the speed of flatbuffer reading + + by re-using the CheckReadBuffer function with the gold data. + """ + + def f(): + CheckReadBuffer(buf, off) + + duration = timeit.timeit(stmt=f, number=count) + rate = float(count) / duration + data = float(len(buf) * count) / float(1024 * 1024) + data_rate = data / float(duration) + + print( + 'traversed %d %d-byte flatbuffers in %.2fsec: %.2f/sec, %.2fMB/sec' + % (count, len(buf), duration, rate, data_rate) + ) + + +def BenchmarkMakeMonsterFromGeneratedCode(count, length): + """BenchmarkMakeMonsterFromGeneratedCode measures the speed of flatbuffer + + creation by re-using the make_monster_from_generated_code function for + generating gold data examples. + """ + + duration = timeit.timeit(stmt=make_monster_from_generated_code, number=count) + rate = float(count) / duration + data = float(length * count) / float(1024 * 1024) + data_rate = data / float(duration) + + print(( + 'built %d %d-byte flatbuffers in %.2fsec: %.2f/sec, %.2fMB/sec' + % (count, length, duration, rate, data_rate) + )) + + +def BenchmarkBuilderClear(count, length): + b = flatbuffers.Builder(length) + duration = timeit.timeit( + stmt=lambda: make_monster_from_generated_code(b), number=count + ) + rate = float(count) / duration + data = float(length * count) / float(1024 * 1024) + data_rate = data / float(duration) + + print(( + 'built %d %d-byte flatbuffers (reused buffer) in %.2fsec:' + ' %.2f/sec, %.2fMB/sec' % (count, length, duration, rate, data_rate) + )) + + +def backward_compatible_run_tests(**kwargs): + if PY_VERSION < (2, 6): + sys.stderr.write('Python version less than 2.6 are not supported') + sys.stderr.flush() + return False + + # python2.6 has a reduced-functionality unittest.main function: + if PY_VERSION == (2, 6): + try: + unittest.main(**kwargs) + except SystemExit as e: + if not e.code == 0: + return False + return True + + # python2.7 and above let us not exit once unittest.main is run: + kwargs['exit'] = False + kwargs['verbosity'] = 0 + ret = unittest.main(**kwargs) + if ret.result.errors or ret.result.failures: + return False + + return True + + +def main(): + import os + import sys + + if not len(sys.argv) == 6: + sys.stderr.write( + 'Usage: %s ' + ' ' + ' \n' + % sys.argv[0] + ) + sys.stderr.write( + ' Provide COMPARE_GENERATED_TO_GO=1 to check' + 'for bytewise comparison to Go data.\n' + ) + sys.stderr.write( + ' Provide COMPARE_GENERATED_TO_JAVA=1 to check' + 'for bytewise comparison to Java data.\n' + ) + sys.stderr.flush() + sys.exit(1) + + kwargs = dict(argv=sys.argv[:-5]) + + create_namespace_shortcut(sys.argv[5].lower() == 'true') + + # show whether numpy is present, as it changes the test logic: + try: + import numpy + + print('numpy available') + except ImportError: + print('numpy not available') + + # run tests, and run some language comparison checks if needed: + success = backward_compatible_run_tests(**kwargs) + if success and os.environ.get('COMPARE_GENERATED_TO_GO', 0) == '1': + success = success and CheckAgainstGoldDataGo() + if success and os.environ.get('COMPARE_GENERATED_TO_JAVA', 0) == '1': + success = success and CheckAgainstGoldDataJava() + + if not success: + sys.stderr.write('Tests failed, skipping benchmarks.\n') + sys.stderr.flush() + sys.exit(1) + + # run benchmarks (if 0, they will be a noop): + bench_vtable = int(sys.argv[1]) + bench_traverse = int(sys.argv[2]) + bench_build = int(sys.argv[3]) + bench_clear = int(sys.argv[4]) + if bench_vtable: + BenchmarkVtableDeduplication(bench_vtable) + if bench_traverse: + buf, off = make_monster_from_generated_code() + BenchmarkCheckReadBuffer(bench_traverse, buf, off) + if bench_build: + buf, off = make_monster_from_generated_code() + BenchmarkMakeMonsterFromGeneratedCode(bench_build, len(buf)) + if bench_clear: + buf, off = make_monster_from_generated_code() + BenchmarkBuilderClear(bench_build, len(buf)) + + +if __name__ == '__main__': + main() diff --git a/tests/rust_usage_test/tests/arrays_test_preserve_case.rs b/tests/rust_usage_test/tests/arrays_test_preserve_case.rs new file mode 100644 index 00000000000..669d599e817 --- /dev/null +++ b/tests/rust_usage_test/tests/arrays_test_preserve_case.rs @@ -0,0 +1,342 @@ +#![no_std] + +#[cfg(not(feature = "no_std"))] +extern crate std; + +extern crate alloc; + +extern crate array_init; + +#[allow(dead_code, unused_imports)] +#[path = "../../preserve_case_rust/arrays_test/mod.rs"] +mod arrays_test_generated; + +use alloc::format; +use core::fmt::Debug; + +use crate::arrays_test_generated::my_game::example::*; + +extern crate quickcheck; + +use array_init::array_init; +use core::mem::size_of; +use quickcheck::{Arbitrary, Gen}; + +fn create_serialized_example_with_generated_code(builder: &mut flatbuffers::FlatBufferBuilder) { + let nested_struct1 = NestedStruct::new( + &[-1, 2], + TestEnum::A, + &[TestEnum::C, TestEnum::B], + &[0x1122334455667788, -0x1122334455667788], + ); + let nested_struct2 = NestedStruct::new( + &[3, -4], + TestEnum::B, + &[TestEnum::B, TestEnum::A], + &[-0x1122334455667788, 0x1122334455667788], + ); + let array_struct = ArrayStruct::new( + 12.34, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF], + -127, + &[nested_struct1, nested_struct2], + 1, + &[-0x8000000000000000, 0x7FFFFFFFFFFFFFFF], + ); + // Test five makes sense when specified. + let ss = ArrayTable::create(builder, &ArrayTableArgs { a: Some(&array_struct) }); + finish_array_table_buffer(builder, ss); +} + +fn serialized_example_is_accessible_and_correct( + bytes: &[u8], + identifier_required: bool, + size_prefixed: bool, +) { + if identifier_required { + let correct = if size_prefixed { + array_table_size_prefixed_buffer_has_identifier(bytes) + } else { + array_table_buffer_has_identifier(bytes) + }; + + assert_eq!(correct, true); + } + + let array_table = if size_prefixed { + size_prefixed_root_as_array_table(bytes).unwrap() + } else { + root_as_array_table(bytes).unwrap() + }; + + let array_struct = array_table.a().unwrap(); + assert_eq!(array_struct.a(), 12.34); + assert_eq!(array_struct.b().len(), 0xF); + assert_eq!(array_struct.b().iter().sum::(), 120); + assert_eq!(array_struct.c(), -127); + + assert_eq!(array_struct.d().len(), 2); + let nested_struct1 = array_struct.d().get(0); + assert_eq!(nested_struct1.a().len(), 2); + assert_eq!(nested_struct1.a().iter().sum::(), 1); + assert_eq!(nested_struct1.b(), TestEnum::A); + assert_eq!(nested_struct1.c().len(), 2); + assert_eq!(nested_struct1.c().get(0), TestEnum::C); + assert_eq!(nested_struct1.c().get(1), TestEnum::B); + assert_eq!(nested_struct1.d().len(), 2); + assert_eq!( + [nested_struct1.d().get(0), nested_struct1.d().get(1)], + [0x1122334455667788, -0x1122334455667788] + ); + let nested_struct2 = array_struct.d().get(1); + assert_eq!(nested_struct2.a().len(), 2); + assert_eq!(nested_struct2.a().iter().sum::(), -1); + assert_eq!(nested_struct2.b(), TestEnum::B); + assert_eq!(nested_struct2.c().len(), 2); + assert_eq!(nested_struct2.c().get(0), TestEnum::B); + assert_eq!(nested_struct2.c().get(1), TestEnum::A); + assert_eq!(nested_struct2.d().len(), 2); + let arr: [i64; 2] = nested_struct2.d().into(); + assert_eq!(arr, [-0x1122334455667788, 0x1122334455667788]); + + assert_eq!(array_struct.e(), 1); + assert_eq!(array_struct.f().len(), 2); + assert_eq!(array_struct.f().get(0), -0x8000000000000000); + assert_eq!(array_struct.f().get(1), 0x7FFFFFFFFFFFFFFF); +} + +#[test] +fn generated_code_creates_correct_example() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(&mut b); + let buf = b.finished_data(); + serialized_example_is_accessible_and_correct(&buf[..], true, false); +} + +#[test] +fn struct_netsted_struct_is_32_bytes() { + assert_eq!(32, ::core::mem::size_of::()); +} + +#[test] +fn struct_array_struct_is_160_bytes() { + assert_eq!(160, ::core::mem::size_of::()); +} + +#[test] +fn test_object_api_reads_correctly() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(&mut b); + + let array_table = root_as_array_table(b.finished_data()).unwrap().unpack(); + + let array_struct = array_table.a.unwrap(); + assert_eq!(array_struct.a, 12.34); + assert_eq!(array_struct.b.len(), 0xF); + assert_eq!(array_struct.b.iter().sum::(), 120); + assert_eq!(array_struct.c, -127); + + assert_eq!(array_struct.d.len(), 2); + let nested_struct1 = &array_struct.d[0]; + assert_eq!(nested_struct1.a.len(), 2); + assert_eq!(nested_struct1.a.iter().sum::(), 1); + assert_eq!(nested_struct1.b, TestEnum::A); + assert_eq!(nested_struct1.c.len(), 2); + assert_eq!(nested_struct1.c[0], TestEnum::C); + assert_eq!(nested_struct1.c[1], TestEnum::B); + assert_eq!(nested_struct1.d.len(), 2); + assert_eq!(nested_struct1.d, [0x1122334455667788, -0x1122334455667788]); + let nested_struct2 = &array_struct.d[1]; + assert_eq!(nested_struct2.a.len(), 2); + assert_eq!(nested_struct2.a.iter().sum::(), -1); + assert_eq!(nested_struct2.b, TestEnum::B); + assert_eq!(nested_struct2.c.len(), 2); + assert_eq!(nested_struct2.c[0], TestEnum::B); + assert_eq!(nested_struct2.c[1], TestEnum::A); + assert_eq!(nested_struct2.d.len(), 2); + assert_eq!(nested_struct2.d, [-0x1122334455667788, 0x1122334455667788]); + + assert_eq!(array_struct.e, 1); + assert_eq!(array_struct.f.len(), 2); + assert_eq!(array_struct.f[0], -0x8000000000000000); + assert_eq!(array_struct.f[1], 0x7FFFFFFFFFFFFFFF); +} + +#[test] +fn object_api_defaults() { + use arrays_test_generated::my_game::example::*; + + assert_eq!( + NestedStructT::default(), + NestedStructT { + a: [0, 0], + b: TestEnum::default(), + c: [TestEnum::default(), TestEnum::default()], + d: [0, 0], + } + ); + + assert_eq!( + ArrayStructT::default(), + ArrayStructT { + a: 0.0, + b: [0; 0xF], + c: 0, + d: [NestedStructT::default(), NestedStructT::default()], + e: 0, + f: [0, 0], + } + ); +} + +#[test] +fn generated_code_debug_prints_correctly() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(b); + let buf = b.finished_data(); + let array_table = root_as_array_table(buf).unwrap(); + assert_eq!( + format!("{:.5?}", &array_table), + "ArrayTable { a: Some(ArrayStruct { a: 12.34000, \ + b: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], \ + c: -127, d: [NestedStruct { a: [-1, 2], b: A, c: [C, B], \ + d: [1234605616436508552, -1234605616436508552] }, \ + NestedStruct { a: [3, -4], b: B, c: [B, A], d: [-1234605616436508552, 1234605616436508552] }], \ + e: 1, f: [-9223372036854775808, 9223372036854775807] }) }" + ); +} + +#[test] +#[should_panic] +fn assert_on_too_small_array_buf() { + let a = [0u8; 19]; + unsafe { flatbuffers::Array::::new(&a) }; +} + +#[test] +#[should_panic] +fn assert_on_too_big_array_buf() { + let a = [0u8; 21]; + unsafe { flatbuffers::Array::::new(&a) }; +} + +#[test] +#[cfg(target_endian = "little")] +fn verify_struct_array_alignment() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(&mut b); + let buf = b.finished_data(); + let array_table = root_as_array_table(buf).unwrap(); + let array_struct = array_table.a().unwrap(); + let struct_start_ptr = array_struct.0.as_ptr() as usize; + let b_start_ptr = array_struct.b().as_ptr() as usize; + let d_start_ptr = array_struct.d().as_ptr() as usize; + // The T type of b + let b_aln = ::core::mem::align_of::(); + assert_eq!((b_start_ptr - struct_start_ptr) % b_aln, 0); + assert_eq!((d_start_ptr - b_start_ptr) % b_aln, 0); + assert_eq!((d_start_ptr - struct_start_ptr) % 8, 0); +} + +#[derive(Clone, Debug)] +struct FakeArray([T; N]); + +impl Arbitrary for FakeArray { + fn arbitrary(g: &mut G) -> FakeArray { + let x: [T; N] = array_init(|_| { + loop { + let generated_scalar = T::arbitrary(g); + // Verify that generated scalar is not Nan, which is not equals to itself, + // therefore we can't use it to validate input == output + if generated_scalar == generated_scalar { + return generated_scalar; + } + } + }); + FakeArray { 0: x } + } +} + +#[cfg(test)] +mod array_fuzz { + extern crate flatbuffers; + #[cfg(not(miri))] // slow. + extern crate quickcheck; + + use self::flatbuffers::{Follow, Push}; + use super::*; + + const MAX_TESTS: u64 = 20; + const ARRAY_SIZE: usize = 29; + + // This uses a macro because lifetimes for the trait-bounded function get too + // complicated. + macro_rules! impl_prop { + ($test_name:ident, $fn_name:ident, $ty:ident) => { + fn $fn_name(xs: FakeArray<$ty, ARRAY_SIZE>) { + let mut test_buf = [0 as u8; 1024]; + let arr: flatbuffers::Array<$ty, ARRAY_SIZE> = unsafe { + flatbuffers::emplace_scalar_array(&mut test_buf, 0, &xs.0); + flatbuffers::Array::follow(&test_buf, 0) + }; + let got: [$ty; ARRAY_SIZE] = arr.into(); + assert_eq!(got, xs.0); + } + #[test] + fn $test_name() { + quickcheck::QuickCheck::new() + .max_tests(MAX_TESTS) + .quickcheck($fn_name as fn(FakeArray<$ty, ARRAY_SIZE>)); + } + }; + } + + impl_prop!(test_bool, prop_bool, bool); + impl_prop!(test_u8, prop_u8, u8); + impl_prop!(test_i8, prop_i8, i8); + impl_prop!(test_u16, prop_u16, u16); + impl_prop!(test_u32, prop_u32, u32); + impl_prop!(test_u64, prop_u64, u64); + impl_prop!(test_i16, prop_i16, i16); + impl_prop!(test_i32, prop_i32, i32); + impl_prop!(test_i64, prop_i64, i64); + impl_prop!(test_f32, prop_f32, f32); + impl_prop!(test_f64, prop_f64, f64); + + const NESTED_STRUCT_SIZE: usize = size_of::(); + + #[derive(Clone, Debug, PartialEq)] + struct NestedStructWrapper(NestedStruct); + + impl Arbitrary for NestedStructWrapper { + fn arbitrary(g: &mut G) -> NestedStructWrapper { + let mut x = NestedStruct::default(); + x.0 = FakeArray::::arbitrary(g).0; + NestedStructWrapper { 0: x } + } + } + + fn prop_struct(xs: FakeArray) { + let mut test_buf = [0 as u8; 1024]; + let native_struct_array: [&NestedStruct; ARRAY_SIZE] = + array_init::from_iter(xs.0.iter().map(|x| &x.0)).unwrap(); + for i in 0..ARRAY_SIZE { + let offset = i * NESTED_STRUCT_SIZE; + unsafe { + native_struct_array[i].push(&mut test_buf[offset..offset + NESTED_STRUCT_SIZE], 0) + }; + } + let arr: flatbuffers::Array = + unsafe { flatbuffers::Array::follow(&test_buf, 0) }; + let got: [&NestedStruct; ARRAY_SIZE] = arr.into(); + assert_eq!(got, native_struct_array); + } + + #[test] + #[cfg(not(miri))] // slow. + fn test_struct() { + quickcheck::QuickCheck::new() + .max_tests(MAX_TESTS) + .quickcheck(prop_struct as fn(FakeArray)); + } +} diff --git a/tests/rust_usage_test/tests/integration_test_preserve_case.rs b/tests/rust_usage_test/tests/integration_test_preserve_case.rs new file mode 100644 index 00000000000..068a48bd123 --- /dev/null +++ b/tests/rust_usage_test/tests/integration_test_preserve_case.rs @@ -0,0 +1,3235 @@ +/* + * + * Copyright 2018 Google Inc. All rights reserved. + * + * 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. + */ + +#![no_std] + +#[cfg(not(feature = "no_std"))] +extern crate std; +#[cfg(not(feature = "no_std"))] +use alloc::vec::Vec; + +#[macro_use] +extern crate alloc; + +use alloc::string::String; + +#[cfg(feature = "no_std")] +#[global_allocator] +static ALLOCATOR: libc_alloc::LibcAlloc = libc_alloc::LibcAlloc; + +#[macro_use] +#[cfg(not(miri))] // slow. +extern crate quickcheck; +extern crate flatbuffers; +extern crate flexbuffers; +extern crate rand; +extern crate serde; +#[macro_use] +extern crate serde_derive; +#[cfg(not(miri))] // slow. +#[macro_use] +extern crate quickcheck_derive; + +mod flexbuffers_tests; +mod more_defaults_test; +mod optional_scalars_test; + +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/include_test1/mod.rs"] +pub mod include_test1_generated; + +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/include_test2/mod.rs"] +pub mod include_test2_generated; + +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/namespace_test/mod.rs"] +pub mod namespace_test_generated; + +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/monster_test/mod.rs"] +mod monster_test_generated; +pub use monster_test_generated::my_game; + +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/optional_scalars/mod.rs"] +mod optional_scalars_generated; + +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/arrays_test/mod.rs"] +mod arrays_test_generated; + +// We use incorrect casing to test keywords. +#[allow(dead_code, unused_imports, non_camel_case_types, non_snake_case)] +#[path = "../../preserve_case_rust/keyword_test/mod.rs"] +mod keyword_test_generated; + +// Test rust namer, should not cause compiling issues +#[allow(dead_code, unused_imports, clippy::all)] +#[path = "../../preserve_case_rust/rust_namer_test/mod.rs"] +mod rust_namer_test; + +#[rustfmt::skip] // TODO: Use standard rust formatting and remove dead code. +#[allow(dead_code)] +mod flatbuffers_tests { +use super::*; + +// Include simple random number generator to ensure results will be the +// same across platforms. +// http://en.wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator +struct LCG(u64); +impl LCG { + fn new() -> Self { + LCG { 0: 48271 } + } + fn next(&mut self) -> u64 { + let old = self.0; + self.0 = (self.0 * 279470273u64) % 4294967291u64; + old + } + fn reset(&mut self) { + self.0 = 48271 + } +} + +// test helper macro to return an error if two expressions are not equal +macro_rules! check_eq { + ($field_call:expr, $want:expr) => ( + if $field_call == $want { + Ok(()) + } else { + Err(stringify!($field_call)) + } + ) +} + +#[test] +fn macro_check_eq() { + assert!(check_eq!(1, 1).is_ok()); + assert!(check_eq!(1, 2).is_err()); +} + +// test helper macro to return an error if two expressions are equal +macro_rules! check_is_some { + ($field_call:expr) => ( + if $field_call.is_some() { + Ok(()) + } else { + Err(stringify!($field_call)) + } + ) +} + +#[test] +fn macro_check_is_some() { + let some: Option = Some(0); + let none: Option = None; + assert!(check_is_some!(some).is_ok()); + assert!(check_is_some!(none).is_err()); +} + +#[test] +fn object_api_defaults() { + use my_game::example::*; + assert_eq!( + Vec3T::default(), Vec3T { + x: 0.0, + y: 0.0, + z: 0.0, + test1: 0.0, + test2: Color::empty(), + test3: TestT { + a: 0, + b: 0 + } + }); + let mut default_without_nan = MonsterT::default(); + default_without_nan.nan_default = 0.0; + assert_eq!( + default_without_nan, + MonsterT { + pos: None, + hp: 100, + mana: 150, + name: String::new(), // required string => default is empty string. + color: Color::Blue, + inventory: None, + testarrayoftables: None, + testarrayofstring: None, + testarrayofstring2: None, + testarrayofbools: None, + testarrayofsortedstruct: None, + enemy: None, + test: AnyT::NONE, + test4: None, + test5: None, + testnestedflatbuffer: None, + testempty: None, + testbool: false, + testhashs32_fnv1: 0, + testhashu32_fnv1: 0, + testhashs64_fnv1: 0, + testhashu64_fnv1: 0, + testhashs32_fnv1a: 0, + testhashu32_fnv1a: 0, + testhashs64_fnv1a: 0, + testhashu64_fnv1a: 0, + testf: 3.14159, + testf2: 3.0, + testf3: 0.0, + flex: None, + vector_of_longs: None, + vector_of_doubles: None, + parent_namespace_test: None, + vector_of_referrables: None, + single_weak_reference: 0, + vector_of_weak_references: None, + vector_of_strong_referrables: None, + co_owning_reference: 0, + vector_of_co_owning_references: None, + non_owning_reference: 0, + vector_of_non_owning_references: None, + any_unique: AnyUniqueAliasesT::NONE, + any_ambiguous: AnyAmbiguousAliasesT::NONE, + vector_of_enums: None, + signed_enum: Race::None, + testrequirednestedflatbuffer: None, // despite the name, it is not required. + scalar_key_sorted_tables: None, + native_inline: None, + long_enum_non_enum_default: Default::default(), + long_enum_normal_default: LongEnum::LongOne, + nan_default: 0.0, + inf_default: f32::INFINITY, + positive_inf_default: f32::INFINITY, + infinity_default: f32::INFINITY, + positive_infinity_default: f32::INFINITY, + negative_inf_default: f32::NEG_INFINITY, + negative_infinity_default: f32::NEG_INFINITY, + double_inf_default: f64::INFINITY, + } + ); +} + +fn create_serialized_example_with_generated_code(builder: &mut flatbuffers::FlatBufferBuilder) { + let mon = { + let s0 = builder.create_string("test1"); + let s1 = builder.create_string("test2"); + let fred_name = builder.create_string("Fred"); + + // can't inline creation of this Vec3 because we refer to it by reference, so it must live + // long enough to be used by MonsterArgs. + let pos = my_game::example::Vec3::new(1.0, 2.0, 3.0, 3.0, my_game::example::Color::Green, &my_game::example::Test::new(5i16, 6i8)); + + let args = my_game::example::MonsterArgs{ + hp: 80, + mana: 150, + name: Some(builder.create_string("MyMonster")), + pos: Some(&pos), + test_type: my_game::example::Any::Monster, + test: Some(my_game::example::Monster::create(builder, &my_game::example::MonsterArgs{ + name: Some(fred_name), + ..Default::default() + }).as_union_value()), + inventory: Some(builder.create_vector(&[0u8, 1, 2, 3, 4])), + test4: Some(builder.create_vector(&[my_game::example::Test::new(10, 20), + my_game::example::Test::new(30, 40)])), + testarrayofstring: Some(builder.create_vector(&[s0, s1])), + ..Default::default() + }; + my_game::example::Monster::create(builder, &args) + }; + my_game::example::finish_monster_buffer(builder, mon); +} + +fn create_serialized_example_with_library_code(builder: &mut flatbuffers::FlatBufferBuilder) { + let nested_union_mon = { + let name = builder.create_string("Fred"); + let table_start = builder.start_table(); + builder.push_slot_always(my_game::example::Monster::VT_NAME, name); + builder.end_table(table_start) + }; + let pos = my_game::example::Vec3::new(1.0, 2.0, 3.0, 3.0, my_game::example::Color::Green, &my_game::example::Test::new(5i16, 6i8)); + let inv = builder.create_vector(&[0u8, 1, 2, 3, 4]); + + let test4 = builder.create_vector(&[my_game::example::Test::new(10, 20), + my_game::example::Test::new(30, 40)][..]); + + let name = builder.create_string("MyMonster"); + + let test1 = builder.create_string("test1"); + let test2 = builder.create_string("test2"); + + let testarrayofstring = builder.create_vector(&[test1, test2]); + + // begin building + + let table_start = builder.start_table(); + builder.push_slot(my_game::example::Monster::VT_HP, 80i16, 100); + builder.push_slot_always(my_game::example::Monster::VT_NAME, name); + builder.push_slot_always(my_game::example::Monster::VT_POS, &pos); + builder.push_slot(my_game::example::Monster::VT_TEST_TYPE, my_game::example::Any::Monster, my_game::example::Any::NONE); + builder.push_slot_always(my_game::example::Monster::VT_TEST, nested_union_mon); + builder.push_slot_always(my_game::example::Monster::VT_INVENTORY, inv); + builder.push_slot_always(my_game::example::Monster::VT_TEST4, test4); + builder.push_slot_always(my_game::example::Monster::VT_TESTARRAYOFSTRING, testarrayofstring); + let root = builder.end_table(table_start); + builder.finish(root, Some(my_game::example::MONSTER_IDENTIFIER)); +} + +fn serialized_example_is_accessible_and_correct(bytes: &[u8], identifier_required: bool, size_prefixed: bool) -> Result<(), &'static str> { + + if identifier_required { + let correct = if size_prefixed { + my_game::example::monster_size_prefixed_buffer_has_identifier(bytes) + } else { + my_game::example::monster_buffer_has_identifier(bytes) + }; + check_eq!(correct, true)?; + } + + let m = if size_prefixed { + my_game::example::size_prefixed_root_as_monster(bytes).unwrap() + } else { + my_game::example::root_as_monster(bytes).unwrap() + }; + + check_eq!(m.hp(), 80)?; + check_eq!(m.mana(), 150)?; + check_eq!(m.name(), "MyMonster")?; + + let pos = m.pos().unwrap(); + check_eq!(pos.x(), 1.0f32)?; + check_eq!(pos.y(), 2.0f32)?; + check_eq!(pos.z(), 3.0f32)?; + check_eq!(pos.test1(), 3.0f64)?; + check_eq!(pos.test2(), my_game::example::Color::Green)?; + + let pos_test3 = pos.test3(); + check_eq!(pos_test3.a(), 5i16)?; + check_eq!(pos_test3.b(), 6i8)?; + + check_eq!(m.test_type(), my_game::example::Any::Monster)?; + check_is_some!(m.test())?; + let table2 = m.test().unwrap(); + let monster2 = unsafe { my_game::example::Monster::init_from_table(table2) }; + + check_eq!(monster2.name(), "Fred")?; + + check_is_some!(m.inventory())?; + let inv = m.inventory().unwrap(); + check_eq!(inv.len(), 5)?; + check_eq!(inv.iter().sum::(), 10u8)?; + check_eq!(inv.iter().rev().sum::(), 10u8)?; + + check_is_some!(m.test4())?; + let test4 = m.test4().unwrap(); + check_eq!(test4.len(), 2)?; + check_eq!(test4.get(0).a() as i32 + test4.get(0).b() as i32 + + test4.get(1).a() as i32 + test4.get(1).b() as i32, 100)?; + + check_is_some!(m.testarrayofstring())?; + let testarrayofstring = m.testarrayofstring().unwrap(); + check_eq!(testarrayofstring.len(), 2)?; + check_eq!(testarrayofstring.get(0), "test1")?; + check_eq!(testarrayofstring.get(1), "test2")?; + + Ok(()) +} + +#[test] +fn test_object_api_reads_correctly() -> Result<(), &'static str>{ + let mut fbb = flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_library_code(&mut fbb); + + let m = my_game::example::root_as_monster(fbb.finished_data()).unwrap().unpack(); + + check_eq!(m.hp, 80)?; + check_eq!(m.mana, 150)?; + check_eq!(m.name, "MyMonster")?; + + let pos = m.pos.as_ref().unwrap(); + check_eq!(pos.x, 1.0f32)?; + check_eq!(pos.y, 2.0f32)?; + check_eq!(pos.z, 3.0f32)?; + check_eq!(pos.test1, 3.0f64)?; + check_eq!(pos.test2, my_game::example::Color::Green)?; + + let pos_test3 = &pos.test3; + check_eq!(pos_test3.a, 5i16)?; + check_eq!(pos_test3.b, 6i8)?; + + let monster2 = m.test.as_monster().unwrap(); + check_eq!(monster2.name, "Fred")?; + + let inv = m.inventory.as_ref().unwrap(); + check_eq!(inv.len(), 5)?; + check_eq!(inv.iter().sum::(), 10u8)?; + check_eq!(inv.iter().rev().sum::(), 10u8)?; + + let test4 = m.test4.as_ref().unwrap(); + check_eq!(test4.len(), 2)?; + check_eq!(test4[0].a as i32 + test4[0].b as i32 + + test4[1].a as i32 + test4[1].b as i32, 100)?; + + let testarrayofstring = m.testarrayofstring.as_ref().unwrap(); + check_eq!(testarrayofstring.len(), 2)?; + check_eq!(testarrayofstring[0], "test1")?; + check_eq!(testarrayofstring[1], "test2")?; + Ok(()) +} + + + +// Disabled due to Windows CI limitations. +// #[test] +// fn builder_initializes_with_maximum_buffer_size() { +// flatbuffers::FlatBufferBuilder::with_capacity(flatbuffers::FLATBUFFERS_MAX_BUFFER_SIZE); +// } + +#[should_panic] +#[test] +fn builder_abort_with_greater_than_maximum_buffer_size() { + flatbuffers::FlatBufferBuilder::with_capacity(flatbuffers::FLATBUFFERS_MAX_BUFFER_SIZE+1); +} + +#[test] +fn builder_collapses_into_vec() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(&mut b); + let (backing_buf, head) = b.collapse(); + serialized_example_is_accessible_and_correct(&backing_buf[head..], true, false).unwrap(); +} + +#[test] +#[cfg(not(miri))] // slow. +fn verifier_one_byte_errors_do_not_crash() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_library_code(&mut b); + let mut badbuf = b.finished_data().to_vec(); + // If the verifier says a buffer is okay then using it won't cause a crash. + // We use write_fmt since Debug visits all the fields - but there's no need to store anything. + struct ForgetfulWriter; + use core::fmt::Write; + impl Write for ForgetfulWriter { + fn write_str(&mut self, _: &str) -> Result<(), core::fmt::Error> { + Ok(()) + } + } + let mut w = ForgetfulWriter; + for d in 1..=255u8 { + for i in 0..badbuf.len() { + let orig = badbuf[i]; + badbuf[i] = badbuf[i].wrapping_add(d); + if let Ok(m) = flatbuffers::root::(&badbuf) { + w.write_fmt(format_args!("{:?}", m)).unwrap() + } + badbuf[i] = orig; + } + } +} +#[test] +#[cfg(not(miri))] // slow. +fn verifier_too_many_tables() { + use my_game::example::*; + let b = &mut flatbuffers::FlatBufferBuilder::new(); + let r = Referrable::create(b, &ReferrableArgs { id: 42 }); + let rs = b.create_vector(&vec![r; 500]); + let name = Some(b.create_string("foo")); + let m = Monster::create(b, &MonsterArgs { + vector_of_referrables: Some(rs), + name, // required field. + ..Default::default() + }); + b.finish(m, None); + + let data = b.finished_data(); + let mut opts = flatbuffers::VerifierOptions::default(); + + opts.max_tables = 500; + let res = flatbuffers::root_with_opts::(&opts, data); + assert_eq!(res.unwrap_err(), flatbuffers::InvalidFlatbuffer::TooManyTables); + + opts.max_tables += 2; + assert!(flatbuffers::root_with_opts::(&opts, data).is_ok()); +} +#[test] +#[cfg(not(miri))] // slow. +fn verifier_apparent_size_too_large() { + use my_game::example::*; + let b = &mut flatbuffers::FlatBufferBuilder::new(); + let name = Some(b.create_string("foo")); + // String amplification attack. + let s = b.create_string(&(core::iter::repeat("X").take(1000).collect::())); + let testarrayofstring = Some(b.create_vector(&vec![s; 1000])); + let m = Monster::create(b, &MonsterArgs { + testarrayofstring, + name, // required field. + ..Default::default() + }); + b.finish(m, None); + let data = b.finished_data(); + assert!(data.len() < 5200); // est 4000 for the vector + 1000 for the string + 200 overhead. + let mut opts = flatbuffers::VerifierOptions::default(); + opts.max_apparent_size = 1_000_000; + + let res = flatbuffers::root_with_opts::(&opts, data); + assert_eq!(res.unwrap_err(), flatbuffers::InvalidFlatbuffer::ApparentSizeTooLarge); + + opts.max_apparent_size += 20_000; + assert!(flatbuffers::root_with_opts::(&opts, data).is_ok()); +} +#[test] +fn verifier_in_too_deep() { + use my_game::example::*; + let b = &mut flatbuffers::FlatBufferBuilder::new(); + let name = Some(b.create_string("foo")); + let mut prev_monster = None; + for _ in 0..11 { + prev_monster = Some(Monster::create(b, &MonsterArgs { + enemy: prev_monster, + name, // required field. + ..Default::default() + })); + }; + b.finish(prev_monster.unwrap(), None); + let mut opts = flatbuffers::VerifierOptions::default(); + opts.max_depth = 10; + + let data = b.finished_data(); + let res = flatbuffers::root_with_opts::(&opts, data); + assert_eq!(res.unwrap_err(), flatbuffers::InvalidFlatbuffer::DepthLimitReached); + + opts.max_depth += 1; + assert!(flatbuffers::root_with_opts::(&opts, data).is_ok()); +} + +#[cfg(test)] +mod generated_constants { + extern crate flatbuffers; + use super::my_game; + + #[test] + fn monster_identifier() { + assert_eq!("MONS", my_game::example::MONSTER_IDENTIFIER); + } + + #[test] + fn monster_file_extension() { + assert_eq!("mon", my_game::example::MONSTER_EXTENSION); + } + + #[test] + fn enum_constants_are_public() { + assert_eq!(-1, my_game::example::Race::ENUM_MIN); + assert_eq!(2, my_game::example::Race::ENUM_MAX); + assert_eq!(my_game::example::Race::ENUM_VALUES, [ + my_game::example::Race::None, + my_game::example::Race::Human, + my_game::example::Race::Dwarf, + my_game::example::Race::Elf, + ]); + + assert_eq!(0, my_game::example::Any::ENUM_MIN); + assert_eq!(3, my_game::example::Any::ENUM_MAX); + assert_eq!(my_game::example::Any::ENUM_VALUES, [ + my_game::example::Any::NONE, + my_game::example::Any::Monster, + my_game::example::Any::TestSimpleTableWithEnum, + my_game::example::Any::MyGame_Example2_Monster, + ]); + + assert_eq!(0, my_game::example::AnyUniqueAliases::ENUM_MIN); + assert_eq!(3, my_game::example::AnyUniqueAliases::ENUM_MAX); + assert_eq!(my_game::example::AnyUniqueAliases::ENUM_VALUES, [ + my_game::example::AnyUniqueAliases::NONE, + my_game::example::AnyUniqueAliases::M, + my_game::example::AnyUniqueAliases::TS, + my_game::example::AnyUniqueAliases::M2, + ]); + + assert_eq!(0, my_game::example::AnyAmbiguousAliases::ENUM_MIN); + assert_eq!(3, my_game::example::AnyAmbiguousAliases::ENUM_MAX); + assert_eq!(my_game::example::AnyAmbiguousAliases::ENUM_VALUES, [ + my_game::example::AnyAmbiguousAliases::NONE, + my_game::example::AnyAmbiguousAliases::M1, + my_game::example::AnyAmbiguousAliases::M2, + my_game::example::AnyAmbiguousAliases::M3, + ]); + } +} + +#[cfg(not(feature = "no_std"))] +#[cfg(test)] +mod lifetime_correctness { + extern crate flatbuffers; + + use core::mem; + + use super::my_game; + use super::load_file; + + #[test] + fn table_get_field_from_static_buffer_1() { + let buf = load_file("../monsterdata_test.mon").expect("missing monsterdata_test.mon"); + // create 'static slice + let slice: &[u8] = &buf; + let slice: &'static [u8] = unsafe { mem::transmute(slice) }; + // make sure values retrieved from the 'static buffer are themselves 'static + let monster: my_game::example::Monster<'static> = my_game::example::root_as_monster(slice).unwrap(); + // this line should compile: + let name: Option<&'static str> = unsafe { monster._tab.get::>(my_game::example::Monster::VT_NAME, None) }; + assert_eq!(name, Some("MyMonster")); + } + + #[test] + fn table_get_field_from_static_buffer_2() { + static DATA: [u8; 4] = [0, 0, 0, 0]; // some binary data + let table: flatbuffers::Table<'static> = unsafe { flatbuffers::Table::new(&DATA, 0) }; + // this line should compile: + unsafe { table.get::<&'static str>(0, None) }; + } + + #[test] + fn table_object_self_lifetime_in_closure() { + // This test is designed to ensure that lifetimes for temporary intermediate tables aren't inflated beyond where the need to be. + let buf = load_file("../monsterdata_test.mon").expect("missing monsterdata_test.mon"); + let monster = my_game::example::root_as_monster(&buf).unwrap(); + let enemy: Option = monster.enemy(); + // This line won't compile if "self" is required to live for the lifetime of buf above as the borrow disappears at the end of the closure. + let enemy_of_my_enemy = enemy.map(|e| { + // enemy (the Option) is consumed, and the enum's value is taken as a temporary (e) at the start of the closure + let name = e.name(); + // ... the temporary dies here, so for this to compile name's lifetime must not be tied to the temporary + name + // If this test fails the error would be "`e` dropped here while still borrowed" + }); + assert_eq!(enemy_of_my_enemy, Some("Fred")); + } +} + +#[cfg(test)] +mod roundtrip_generated_code { + extern crate flatbuffers; + + use alloc::vec::Vec; + + use super::my_game; + + fn build_mon<'a, 'b>(builder: &'a mut flatbuffers::FlatBufferBuilder, args: &'b my_game::example::MonsterArgs) -> my_game::example::Monster<'a> { + let mon = my_game::example::Monster::create(builder, &args); + my_game::example::finish_monster_buffer(builder, mon); + my_game::example::root_as_monster(builder.finished_data()).unwrap() + } + + #[test] + fn scalar_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{hp: 123, name: Some(name), ..Default::default()}); + assert_eq!(m.hp(), 123); + } + #[test] + fn scalar_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.hp(), 100); + } + #[test] + fn string_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foobar"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.name(), "foobar"); + } + #[test] + fn struct_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + pos: Some(&my_game::example::Vec3::new(1.0, 2.0, 3.0, 4.0, + my_game::example::Color::Green, + &my_game::example::Test::new(98, 99))), + ..Default::default() + }); + assert_eq!(m.pos(), Some(&my_game::example::Vec3::new(1.0, 2.0, 3.0, 4.0, + my_game::example::Color::Green, + &my_game::example::Test::new(98, 99)))); + } + #[test] + fn struct_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.pos(), None); + } + #[test] + fn enum_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), color: my_game::example::Color::Red, ..Default::default()}); + assert_eq!(m.color(), my_game::example::Color::Red); + } + #[test] + fn enum_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.color(), my_game::example::Color::Blue); + } + #[test] + fn union_store() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + { + let name_inner = b.create_string("foo"); + let name_outer = b.create_string("bar"); + + let inner = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name_inner), + ..Default::default() + }); + let outer = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name_outer), + test_type: my_game::example::Any::Monster, + test: Some(inner.as_union_value()), + ..Default::default() + }); + my_game::example::finish_monster_buffer(b, outer); + } + + let mon = my_game::example::root_as_monster(b.finished_data()).unwrap(); + assert_eq!(mon.name(), "bar"); + assert_eq!(mon.test_type(), my_game::example::Any::Monster); + let name = unsafe { my_game::example::Monster::init_from_table(mon.test().unwrap()).name() }; + assert_eq!(name, "foo"); + assert_eq!(mon.test_as_monster().unwrap().name(), "foo"); + assert_eq!(mon.test_as_test_simple_table_with_enum(), None); + assert_eq!(mon.test_as_my_game_example_2_monster(), None); + } + #[test] + fn union_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.test_type(), my_game::example::Any::NONE); + assert_eq!(m.test(), None); + } + #[test] + fn table_full_namespace_store() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + { + let name_inner = b.create_string("foo"); + let name_outer = b.create_string("bar"); + + let inner = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name_inner), + ..Default::default() + }); + let outer = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name_outer), + enemy: Some(inner), + ..Default::default() + }); + my_game::example::finish_monster_buffer(b, outer); + } + + let mon = my_game::example::root_as_monster(b.finished_data()).unwrap(); + assert_eq!(mon.name(), "bar"); + assert_eq!(mon.enemy().unwrap().name(), "foo"); + } + #[test] + fn table_full_namespace_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.enemy(), None); + } + #[test] + fn table_store() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + { + let id_inner = b.create_string("foo"); + let name_outer = b.create_string("bar"); + + let inner = my_game::example::Stat::create(b, &my_game::example::StatArgs{ + id: Some(id_inner), + ..Default::default() + }); + let outer = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name_outer), + testempty: Some(inner), + ..Default::default() + }); + my_game::example::finish_monster_buffer(b, outer); + } + + let mon = my_game::example::root_as_monster(b.finished_data()).unwrap(); + assert_eq!(mon.name(), "bar"); + assert_eq!(mon.testempty().unwrap().id(), Some("foo")); + } + #[test] + fn table_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert_eq!(m.testempty(), None); + } + #[test] + fn nested_flatbuffer_store() { + let b0 = { + let mut b0 = flatbuffers::FlatBufferBuilder::new(); + let args = my_game::example::MonsterArgs{ + hp: 123, + name: Some(b0.create_string("foobar")), + ..Default::default() + }; + let mon = my_game::example::Monster::create(&mut b0, &args); + my_game::example::finish_monster_buffer(&mut b0, mon); + b0 + }; + + let b1 = { + let mut b1 = flatbuffers::FlatBufferBuilder::new(); + let args = my_game::example::MonsterArgs{ + testnestedflatbuffer: Some(b1.create_vector(b0.finished_data())), + name: Some(b1.create_string("foo")), + ..Default::default() + }; + let mon = my_game::example::Monster::create(&mut b1, &args); + my_game::example::finish_monster_buffer(&mut b1, mon); + b1 + }; + + let m = my_game::example::root_as_monster(b1.finished_data()).unwrap(); + + assert!(m.testnestedflatbuffer().is_some()); + assert_eq!(m.testnestedflatbuffer().unwrap().bytes(), b0.finished_data()); + + let m2_a = my_game::example::root_as_monster(m.testnestedflatbuffer().unwrap().bytes()).unwrap(); + assert_eq!(m2_a.hp(), 123); + assert_eq!(m2_a.name(), "foobar"); + + assert!(m.testnestedflatbuffer_nested_flatbuffer().is_some()); + let m2_b = m.testnestedflatbuffer_nested_flatbuffer().unwrap(); + + assert_eq!(m2_b.hp(), 123); + assert_eq!(m2_b.name(), "foobar"); + } + #[test] + fn nested_flatbuffer_default() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{name: Some(name), ..Default::default()}); + assert!(m.testnestedflatbuffer().is_none()); + } + #[test] + fn vector_of_string_store_helper_build() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let strings = &[b.create_string("foobar"), b.create_string("baz")]; + let v = b.create_vector(strings); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + testarrayofstring: Some(v), ..Default::default()}); + assert_eq!(m.testarrayofstring().unwrap().len(), 2); + assert_eq!(m.testarrayofstring().unwrap().get(0), "foobar"); + assert_eq!(m.testarrayofstring().unwrap().get(1), "baz"); + + let rust_vec_inst = m.testarrayofstring().unwrap(); + let rust_vec_iter_collect = rust_vec_inst.iter().collect::>(); + assert_eq!(rust_vec_iter_collect.len(), 2); + assert_eq!(rust_vec_iter_collect[0], "foobar"); + assert_eq!(rust_vec_iter_collect[1], "baz"); + + let rust_vec_iter_rev_collect = rust_vec_inst.iter().rev().collect::>(); + assert_eq!(rust_vec_iter_rev_collect.len(), 2); + assert_eq!(rust_vec_iter_rev_collect[1], "foobar"); + assert_eq!(rust_vec_iter_rev_collect[0], "baz"); + + } + #[test] + fn vector_of_string_store_manual_build() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let s0 = b.create_string("foobar"); + let s1 = b.create_string("baz"); + let v = b.create_vector(&[s0, s1]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + testarrayofstring: Some(v), ..Default::default()}); + assert_eq!(m.testarrayofstring().unwrap().len(), 2); + assert_eq!(m.testarrayofstring().unwrap().get(0), "foobar"); + assert_eq!(m.testarrayofstring().unwrap().get(1), "baz"); + + let rust_vec_inst = m.testarrayofstring().unwrap(); + let rust_vec_iter_collect = rust_vec_inst.iter().collect::>(); + assert_eq!(rust_vec_iter_collect.len(), 2); + assert_eq!(rust_vec_iter_collect[0], "foobar"); + assert_eq!(rust_vec_iter_collect[1], "baz"); + + let rust_vec_iter_rev_collect = rust_vec_inst.iter().rev().collect::>(); + assert_eq!(rust_vec_iter_rev_collect.len(), 2); + assert_eq!(rust_vec_iter_rev_collect[0], "baz"); + assert_eq!(rust_vec_iter_rev_collect[1], "foobar"); + } + #[test] + fn vector_of_ubyte_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let v = b.create_vector(&[123u8, 234u8][..]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + inventory: Some(v), ..Default::default() + }); + assert_eq!(m.inventory().unwrap().bytes(), &[123, 234]); + } + #[test] + fn vector_of_bool_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let v = b.create_vector(&[false, true, false, true][..]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + testarrayofbools: Some(v), ..Default::default()}); + + let rust_vec_inst = m.testarrayofbools().unwrap(); + let rust_vec_iter_collect = rust_vec_inst.iter().collect::>(); + assert_eq!(&rust_vec_iter_collect, &[false, true, false, true]); + + let rust_vec_iter_rev_collect = rust_vec_inst.iter().rev().collect::>(); + assert_eq!(&rust_vec_iter_rev_collect, &[true, false, true, false]); + } + #[test] + fn vector_of_f64_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let v = b.create_vector(&[3.14159265359f64][..]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + vector_of_doubles: Some(v), ..Default::default()}); + assert_eq!(m.vector_of_doubles().unwrap().len(), 1); + assert_eq!(m.vector_of_doubles().unwrap().get(0), 3.14159265359f64); + + let rust_vec_inst = m.vector_of_doubles().unwrap(); + let rust_vec_iter_collect = rust_vec_inst.iter().collect::>(); + assert_eq!(rust_vec_iter_collect.len(), 1); + assert_eq!(rust_vec_iter_collect[0], 3.14159265359f64); + + let rust_vec_iter_rev_collect = rust_vec_inst.iter().rev().collect::>(); + assert_eq!(rust_vec_iter_rev_collect.len(), 1); + assert_eq!(rust_vec_iter_rev_collect[0], 3.14159265359f64); + } + #[test] + fn vector_of_struct_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let v = b.create_vector(&[my_game::example::Test::new(127, -128), my_game::example::Test::new(3, 123)][..]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + test4: Some(v), ..Default::default()}); + + let rust_vec_inst = m.test4().unwrap(); + let rust_vec_iter_collect = rust_vec_inst.iter().collect::>(); + assert_eq!(rust_vec_iter_collect, &[&my_game::example::Test::new(127, -128), &my_game::example::Test::new(3, 123)][..]); + + let rust_vec_iter_rev_collect = rust_vec_inst.iter().rev().collect::>(); + assert_eq!(rust_vec_iter_rev_collect, &[&my_game::example::Test::new(3, 123), &my_game::example::Test::new(127, -128)][..]); + } + #[test] + fn vector_of_struct_store_with_type_inference() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let v = b.create_vector(&[my_game::example::Test::new(127, -128), + my_game::example::Test::new(3, 123), + my_game::example::Test::new(100, 101)]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + test4: Some(v), ..Default::default()}); + let vals: Vec<_> = m.test4().unwrap().iter().collect::>(); + assert_eq!(vals, vec![&my_game::example::Test::new(127, -128), &my_game::example::Test::new(3, 123), &my_game::example::Test::new(100, 101)]); + } + #[test] + fn vector_of_enums_store() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let v = b.create_vector::(&[my_game::example::Color::Red, my_game::example::Color::Green][..]); + let name = b.create_string("foo"); + let m = build_mon(&mut b, &my_game::example::MonsterArgs{ + name: Some(name), + vector_of_enums: Some(v), ..Default::default()}); + assert_eq!(m.vector_of_enums().unwrap().len(), 2); + assert_eq!(m.vector_of_enums().unwrap().get(0), my_game::example::Color::Red); + assert_eq!(m.vector_of_enums().unwrap().get(1), my_game::example::Color::Green); + } + #[test] + fn vector_of_table_store() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + let t0 = { + let name = b.create_string("foo"); + let args = my_game::example::MonsterArgs{hp: 55, name: Some(name), ..Default::default()}; + my_game::example::Monster::create(b, &args) + }; + let t1 = { + let name = b.create_string("bar"); + let args = my_game::example::MonsterArgs{name: Some(name), ..Default::default()}; + my_game::example::Monster::create(b, &args) + }; + let v = b.create_vector(&[t0, t1][..]); + let name = b.create_string("foo"); + let m = build_mon(b, &my_game::example::MonsterArgs{ + name: Some(name), + testarrayoftables: Some(v), ..Default::default()}); + assert_eq!(m.testarrayoftables().unwrap().len(), 2); + assert_eq!(m.testarrayoftables().unwrap().get(0).hp(), 55); + assert_eq!(m.testarrayoftables().unwrap().get(0).name(), "foo"); + assert_eq!(m.testarrayoftables().unwrap().get(1).hp(), 100); + assert_eq!(m.testarrayoftables().unwrap().get(1).name(), "bar"); + + let rust_vec_inst = m.testarrayoftables().unwrap(); + let rust_vec_iter_collect = rust_vec_inst.iter().collect::>(); + assert_eq!(rust_vec_iter_collect.len(), 2); + assert_eq!(rust_vec_iter_collect[0].hp(), 55); + assert_eq!(rust_vec_iter_collect[0].name(), "foo"); + assert_eq!(rust_vec_iter_collect[1].hp(), 100); + assert_eq!(rust_vec_iter_collect[1].name(), "bar"); + + let rust_vec_iter_rev_collect = rust_vec_inst.iter().rev().collect::>(); + assert_eq!(rust_vec_iter_rev_collect.len(), 2); + assert_eq!(rust_vec_iter_rev_collect[0].hp(), 100); + assert_eq!(rust_vec_iter_rev_collect[0].name(), "bar"); + assert_eq!(rust_vec_iter_rev_collect[1].hp(), 55); + assert_eq!(rust_vec_iter_rev_collect[1].name(), "foo"); + } +} + +#[cfg(test)] +mod generated_code_alignment_and_padding { + extern crate flatbuffers; + use super::my_game; + + #[test] + fn enum_color_is_1_byte() { + assert_eq!(1, ::core::mem::size_of::()); + } + + #[test] + fn union_any_is_1_byte() { + assert_eq!(1, ::core::mem::size_of::()); + } + + #[test] + fn union_any_is_aligned_to_1() { + assert_eq!(1, ::core::mem::align_of::()); + } + #[test] + fn struct_test_is_4_bytes() { + assert_eq!(4, ::core::mem::size_of::()); + } + #[test] + fn struct_vec3_is_32_bytes() { + assert_eq!(32, ::core::mem::size_of::()); + } + + #[test] + fn struct_vec3_is_written_with_correct_alignment_in_table() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + { + let name = b.create_string("foo"); + let mon = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name), + pos: Some(&my_game::example::Vec3::new(1.0, 2.0, 3.0, 4.0, + my_game::example::Color::Green, + &my_game::example::Test::new(98, 99))), + ..Default::default()}); + my_game::example::finish_monster_buffer(b, mon); + } + let buf = b.finished_data(); + let mon = my_game::example::root_as_monster(buf).unwrap(); + let vec3 = mon.pos().unwrap(); + + let start_ptr = buf.as_ptr() as usize; + let vec3_ptr = vec3 as *const my_game::example::Vec3 as usize; + + assert!(vec3_ptr > start_ptr); + // Vec3 is aligned to 8 wrt the flatbuffer. + assert_eq!((vec3_ptr - start_ptr) % 8, 0); + } + + #[test] + fn struct_ability_is_8_bytes() { + assert_eq!(8, ::core::mem::size_of::()); + } + + #[test] + fn struct_ability_is_written_with_correct_alignment_in_table_vector() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + { + let name = b.create_string("foo"); + let v = b.create_vector(&[my_game::example::Ability::new(1, 2), + my_game::example::Ability::new(3, 4), + my_game::example::Ability::new(5, 6)]); + let mon = my_game::example::Monster::create(b, &my_game::example::MonsterArgs{ + name: Some(name), + testarrayofsortedstruct: Some(v), + ..Default::default()}); + my_game::example::finish_monster_buffer(b, mon); + } + let buf = b.finished_data(); + let mon = my_game::example::root_as_monster(buf).unwrap(); + let abilities = mon.testarrayofsortedstruct().unwrap(); + + let start_ptr = buf.as_ptr() as usize; + for a in abilities.iter() { + let a_ptr = a as *const my_game::example::Ability as usize; + assert!(a_ptr > start_ptr); + let aln = ::core::mem::align_of::(); + assert_eq!((a_ptr - start_ptr) % aln, 0); + } + for a in abilities.iter().rev() { + let a_ptr = a as *const my_game::example::Ability as usize; + assert!(a_ptr > start_ptr); + // Vec3 is aligned to 8 wrt the flatbuffer. + assert_eq!((a_ptr - start_ptr) % 8, 0); + } + } +} + +#[cfg(not(miri))] +quickcheck! { + fn struct_of_structs( + a_id: u32, + a_distance: u32, + b_a: i16, + b_b: i8, + c_id: u32, + c_distance: u32 + ) -> bool { + use my_game::example::*; + let mut sos = StructOfStructs::default(); + let mut a = Ability::default(); + a.set_id(a_id); + a.set_distance(a_distance); + let mut b = Test::default(); + b.set_a(b_a); + b.set_b(b_b); + let mut c = Ability::default(); + c.set_id(c_id); + c.set_distance(c_distance); + sos.set_a(&a); + sos.set_b(&b); + sos.set_c(&c); + + sos.a().id() == a_id && + sos.a().distance() == a_distance && + sos.b().a() == b_a && + sos.b().b() == b_b && + sos.c().id() == c_id && + sos.c().distance() == c_distance + } +} + +#[cfg(not(miri))] // slow. +#[cfg(test)] +mod roundtrip_vectors { + + #[cfg(test)] + mod scalar { + extern crate quickcheck; + extern crate flatbuffers; + + use alloc::vec::Vec; + + const N: u64 = 20; + + fn prop(xs: Vec) + where + T: for<'a> flatbuffers::Follow<'a, Inner = T> + + flatbuffers::EndianScalar + + flatbuffers::Push + + ::core::fmt::Debug, + { + use flatbuffers::Follow; + + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(xs.len()); + for i in (0..xs.len()).rev() { + b.push::(xs[i]); + } + let vecend = b.end_vector::(xs.len()); + b.finish_minimal(vecend); + + let buf = b.finished_data(); + + let got = unsafe { >>::follow(&buf[..], 0) }; + let mut result_vec: Vec = Vec::with_capacity(got.len()); + for i in 0..got.len() { + result_vec.push(got.get(i)); + } + assert_eq!(result_vec, xs); + + let rust_vec_iter = got.iter().collect::>(); + assert_eq!(rust_vec_iter, xs); + + let mut rust_vec_rev_iter = got.iter().rev().collect::>(); + rust_vec_rev_iter.reverse(); + assert_eq!(rust_vec_rev_iter, xs); + } + + #[test] + fn easy_u8() { + prop::(vec![]); + prop::(vec![1u8]); + prop::(vec![1u8, 2u8]); + prop::(vec![1u8, 2u8, 3u8]); + prop::(vec![1u8, 2u8, 3u8, 4u8]); + } + + #[test] + fn fuzz_bool() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_u8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_i8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_u16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_i16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_u32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_i32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_u64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_i64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_f32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + #[test] + fn fuzz_f64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(Vec<_>)); } + } + + #[cfg(test)] + mod string_manual_build { + #[cfg(not(miri))] // slow. + extern crate quickcheck; + extern crate flatbuffers; + + use alloc::string::String; + use alloc::vec::Vec; + + fn prop(xs: Vec) { + use flatbuffers::Follow; + + let mut b = flatbuffers::FlatBufferBuilder::new(); + let mut offsets = Vec::new(); + for s in xs.iter().rev() { + offsets.push(b.create_string(s.as_str())); + } + + b.start_vector::>(xs.len()); + for &i in offsets.iter() { + b.push(i); + } + let vecend = b.end_vector::>(xs.len()); + + b.finish_minimal(vecend); + + let buf = b.finished_data(); + let got = unsafe { >>>::follow(buf, 0) }; + + assert_eq!(got.len(), xs.len()); + for i in 0..xs.len() { + assert_eq!(got.get(i), &xs[i][..]); + } + } + + #[test] + fn fuzz() { + quickcheck::QuickCheck::new().max_tests(20).quickcheck(prop as fn(Vec<_>)); + } + } + + #[cfg(test)] + mod string_helper_build { + #[cfg(not(miri))] // slow. + extern crate quickcheck; + extern crate flatbuffers; + + use alloc::string::String; + use alloc::vec::Vec; + + fn prop(input: Vec) { + use flatbuffers::Follow; + + let mut b = flatbuffers::FlatBufferBuilder::new(); + let xs: Vec<_> = input.iter().map(|s: &String| b.create_string(s)).collect(); + let vecend = b.create_vector(&xs); + + b.finish_minimal(vecend); + + let buf = b.finished_data(); + let got = unsafe { >>>::follow(buf, 0) }; + + assert_eq!(got.len(), xs.len()); + for (idx, s) in input.iter().enumerate() { + assert_eq!(got.get(idx), s); + } + } + + #[test] + fn fuzz() { + quickcheck::QuickCheck::new().max_tests(100).quickcheck(prop as fn(Vec<_>)); + } + } + + #[cfg(test)] + mod ubyte { + #[cfg(not(miri))] // slow. + extern crate quickcheck; + extern crate flatbuffers; + + use alloc::vec::Vec; + + #[cfg(not(miri))] // slow. + #[test] + fn fuzz_manual_build() { + fn prop(vec: Vec) { + let xs = &vec[..]; + + let mut b1 = flatbuffers::FlatBufferBuilder::new(); + b1.start_vector::(xs.len()); + + for i in (0..xs.len()).rev() { + b1.push(xs[i]); + } + b1.end_vector::(xs.len()); + + let mut b2 = flatbuffers::FlatBufferBuilder::new(); + b2.create_vector(xs); + assert_eq!(b1.unfinished_data(), b2.unfinished_data()); + } + quickcheck::QuickCheck::new().max_tests(100).quickcheck(prop as fn(Vec<_>)); + } + } +} + +#[cfg(test)] +mod framing_format { + extern crate flatbuffers; + + use super::my_game; + + #[test] + fn test_size_prefixed_buffer() { + // Create size prefixed buffer. + let mut b = flatbuffers::FlatBufferBuilder::new(); + let args = &my_game::example::MonsterArgs{ + mana: 200, + hp: 300, + name: Some(b.create_string("bob")), + ..Default::default() + }; + let mon = my_game::example::Monster::create(&mut b, &args); + b.finish_size_prefixed(mon, None); + + // Access it. + let buf = b.finished_data(); + let m = flatbuffers::size_prefixed_root::(buf).unwrap(); + assert_eq!(m.mana(), 200); + assert_eq!(m.hp(), 300); + assert_eq!(m.name(), "bob"); + } +} + +#[cfg(not(feature = "no_std"))] +#[cfg(test)] +mod roundtrip_table { + use alloc::string::String; + use alloc::vec::Vec; + use std::collections::HashMap; + + extern crate flatbuffers; + #[cfg(not(miri))] // slow. + extern crate quickcheck; + + use super::LCG; + + #[test] + #[cfg(not(miri))] // slow. + fn table_of_mixed_scalars_fuzz() { + // Values we're testing against: chosen to ensure no bits get chopped + // off anywhere, and also be different from eachother. + let bool_val: bool = true; + let char_val: i8 = -127; // 0x81 + let uchar_val: u8 = 0xFF; + let short_val: i16 = -32222; // 0x8222; + let ushort_val: u16 = 0xFEEE; + let int_val: i32 = unsafe { ::core::mem::transmute(0x83333333u32) }; + let uint_val: u32 = 0xFDDDDDDD; + let long_val: i64 = unsafe { ::core::mem::transmute(0x8444444444444444u64) }; // TODO: byte literal? + let ulong_val: u64 = 0xFCCCCCCCCCCCCCCCu64; + let float_val: f32 = 3.14159; + let double_val: f64 = 3.14159265359; + + let test_value_types_max: isize = 11; + let max_fields_per_object: flatbuffers::VOffsetT = 100; + let num_fuzz_objects: isize = 1000; // The higher, the more thorough :) + + let mut builder = flatbuffers::FlatBufferBuilder::new(); + let mut lcg = LCG::new(); + + let mut objects: Vec = vec![0; num_fuzz_objects as usize]; + + // Generate num_fuzz_objects random objects each consisting of + // fields_per_object fields, each of a random type. + for i in 0..(num_fuzz_objects as usize) { + let fields_per_object = (lcg.next() % (max_fields_per_object as u64)) as flatbuffers::VOffsetT; + let start = builder.start_table(); + + for j in 0..fields_per_object { + let choice = lcg.next() % (test_value_types_max as u64); + + let f = flatbuffers::field_index_to_field_offset(j); + + match choice { + 0 => {builder.push_slot::(f, bool_val, false);} + 1 => {builder.push_slot::(f, char_val, 0);} + 2 => {builder.push_slot::(f, uchar_val, 0);} + 3 => {builder.push_slot::(f, short_val, 0);} + 4 => {builder.push_slot::(f, ushort_val, 0);} + 5 => {builder.push_slot::(f, int_val, 0);} + 6 => {builder.push_slot::(f, uint_val, 0);} + 7 => {builder.push_slot::(f, long_val, 0);} + 8 => {builder.push_slot::(f, ulong_val, 0);} + 9 => {builder.push_slot::(f, float_val, 0.0);} + 10 => {builder.push_slot::(f, double_val, 0.0);} + _ => { panic!("unknown choice: {}", choice); } + } + } + objects[i] = builder.end_table(start).value(); + } + + // Do some bookkeeping to generate stats on fuzzes: + let mut stats: HashMap = HashMap::new(); + let mut values_generated: u64 = 0; + + // Embrace PRNG determinism: + lcg.reset(); + + // Test that all objects we generated are readable and return the + // expected values. We generate random objects in the same order + // so this is deterministic: + for i in 0..(num_fuzz_objects as usize) { + let table = { + let buf = builder.unfinished_data(); + let loc = buf.len() as flatbuffers::UOffsetT - objects[i]; + unsafe { flatbuffers::Table::new(buf, loc as usize) } + }; + + let fields_per_object = (lcg.next() % (max_fields_per_object as u64)) as flatbuffers::VOffsetT; + for j in 0..fields_per_object { + let choice = lcg.next() % (test_value_types_max as u64); + + *stats.entry(choice).or_insert(0) += 1; + values_generated += 1; + + let f = flatbuffers::field_index_to_field_offset(j); + + unsafe { + match choice { + 0 => { assert_eq!(bool_val, table.get::(f, Some(false)).unwrap()); } + 1 => { assert_eq!(char_val, table.get::(f, Some(0)).unwrap()); } + 2 => { assert_eq!(uchar_val, table.get::(f, Some(0)).unwrap()); } + 3 => { assert_eq!(short_val, table.get::(f, Some(0)).unwrap()); } + 4 => { assert_eq!(ushort_val, table.get::(f, Some(0)).unwrap()); } + 5 => { assert_eq!(int_val, table.get::(f, Some(0)).unwrap()); } + 6 => { assert_eq!(uint_val, table.get::(f, Some(0)).unwrap()); } + 7 => { assert_eq!(long_val, table.get::(f, Some(0)).unwrap()); } + 8 => { assert_eq!(ulong_val, table.get::(f, Some(0)).unwrap()); } + 9 => { assert_eq!(float_val, table.get::(f, Some(0.0)).unwrap()); } + 10 => { assert_eq!(double_val, table.get::(f, Some(0.0)).unwrap()); } + _ => { panic!("unknown choice: {}", choice); } + } + } + } + } + + // Assert that we tested all the fuzz cases enough: + let min_tests_per_choice = 1000; + assert!(values_generated > 0); + assert!(min_tests_per_choice > 0); + for i in 0..test_value_types_max as u64 { + assert!(stats[&i] >= min_tests_per_choice, "inadequately-tested fuzz case: {}", i); + } + } + + #[test] + #[cfg(not(miri))] // slow. + fn table_of_byte_strings_fuzz() { + fn prop(vec: Vec>) { + use flatbuffers::field_index_to_field_offset as fi2fo; + use flatbuffers::Follow; + + let xs = &vec[..]; + + // build + let mut b = flatbuffers::FlatBufferBuilder::new(); + let str_offsets: Vec> = xs.iter().map(|s| b.create_byte_string(&s[..])).collect(); + let table_start = b.start_table(); + + for i in 0..xs.len() { + b.push_slot_always(fi2fo(i as flatbuffers::VOffsetT), str_offsets[i]); + } + let root = b.end_table(table_start); + b.finish_minimal(root); + + // use + let buf = b.finished_data(); + let tab = unsafe { >::follow(buf, 0) }; + + for i in 0..xs.len() { + let v = unsafe { tab.get::>>(fi2fo(i as flatbuffers::VOffsetT), None) }; + assert!(v.is_some()); + let v2 = v.unwrap(); + assert_eq!(v2.bytes(), &xs[i]); + } + } + prop(vec![vec![1,2,3]]); + + let n = 20; + quickcheck::QuickCheck::new().max_tests(n).quickcheck(prop as fn(Vec<_>)); + } + + #[test] + #[cfg(not(miri))] // slow. + fn fuzz_table_of_strings() { + fn prop(vec: Vec) { + use flatbuffers::field_index_to_field_offset as fi2fo; + use flatbuffers::Follow; + + let xs = &vec[..]; + + // build + let mut b = flatbuffers::FlatBufferBuilder::new(); + let str_offsets: Vec> = xs.iter().map(|s| b.create_string(&s[..])).collect(); + let table_start = b.start_table(); + + for i in 0..xs.len() { + b.push_slot_always(fi2fo(i as flatbuffers::VOffsetT), str_offsets[i]); + } + let root = b.end_table(table_start); + b.finish_minimal(root); + + // use + let buf = b.finished_data(); + let tab = unsafe { >::follow(buf, 0) }; + + for i in 0..xs.len() { + let v = unsafe { tab.get::>(fi2fo(i as flatbuffers::VOffsetT), None) }; + assert_eq!(v, Some(&xs[i][..])); + } + } + let n = 20; + quickcheck::QuickCheck::new().max_tests(n).quickcheck(prop as fn(Vec)); + } + + #[cfg(not(miri))] // slow. + mod table_of_vectors_of_scalars { + + use alloc::vec::Vec; + + extern crate flatbuffers; + #[cfg(not(miri))] // slow. + extern crate quickcheck; + + const N: u64 = 20; + + fn prop(vecs: Vec>) + where + T: for<'a> flatbuffers::Follow<'a, Inner = T> + + flatbuffers::EndianScalar + + flatbuffers::Push + + ::core::fmt::Debug, + { + use flatbuffers::field_index_to_field_offset as fi2fo; + use flatbuffers::Follow; + + // build + let mut b = flatbuffers::FlatBufferBuilder::new(); + let mut offs = vec![]; + for vec in &vecs { + b.start_vector::(vec.len()); + + let xs = &vec[..]; + for i in (0..xs.len()).rev() { + b.push::(xs[i]); + } + let vecend = b.end_vector::(xs.len()); + offs.push(vecend); + } + + let table_start = b.start_table(); + + for i in 0..vecs.len() { + b.push_slot_always(fi2fo(i as flatbuffers::VOffsetT), offs[i]); + } + let root = b.end_table(table_start); + b.finish_minimal(root); + + // use + let buf = b.finished_data(); + let tab = unsafe { >::follow(buf, 0) }; + + for i in 0..vecs.len() { + let got = unsafe { tab.get::>>(fi2fo(i as flatbuffers::VOffsetT), None) }; + assert!(got.is_some()); + let got2 = got.unwrap(); + let mut got3: Vec = Vec::with_capacity(got2.len()); + for i in 0..got2.len() { + got3.push(got2.get(i)); + } + assert_eq!(vecs[i], got3); + } + } + + #[test] + fn fuzz_bool() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + + #[test] + fn fuzz_u8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_u16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_u32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_u64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + + #[test] + fn fuzz_i8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_i16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_i32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_i64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + + #[test] + fn fuzz_f32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + #[test] + fn fuzz_f64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop as fn(Vec>)); } + } +} + +#[cfg(not(miri))] // slow. +#[cfg(test)] +mod roundtrip_scalars { + extern crate flatbuffers; + #[cfg(not(miri))] // slow. + extern crate quickcheck; + + const N: u64 = 1000; + + fn prop(x: T) { + let mut buf = vec![0u8; ::core::mem::size_of::()]; + let y = unsafe { + flatbuffers::emplace_scalar(&mut buf[..], x); + flatbuffers::read_scalar(&buf[..]) + }; + assert_eq!(x, y); + } + + #[test] + fn fuzz_bool() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + #[test] + fn fuzz_u8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + #[test] + fn fuzz_i8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + + #[test] + fn fuzz_u16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + #[test] + fn fuzz_i16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + + #[test] + fn fuzz_u32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + #[test] + fn fuzz_i32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + + #[test] + fn fuzz_u64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + #[test] + fn fuzz_i64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + + #[test] + fn fuzz_f32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } + #[test] + fn fuzz_f64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop:: as fn(_)); } +} + +#[cfg(test)] +#[cfg(not(miri))] // slow. +mod roundtrip_push_follow_scalars { + extern crate flatbuffers; + #[cfg(not(miri))] // slow. + extern crate quickcheck; + + use flatbuffers::Push; + + const N: u64 = 1000; + + // This uses a macro because lifetimes for a trait-bounded function get too + // complicated. + macro_rules! impl_prop { + ($fn_name:ident, $ty:ident) => ( + fn $fn_name(x: $ty) { + let mut buf = vec![0u8; ::core::mem::size_of::<$ty>()]; + unsafe { x.push(&mut buf[..], 0) }; + let fs: flatbuffers::FollowStart<$ty> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { fs.self_follow(&buf[..], 0) }, x); + } + ) + } + + impl_prop!(prop_bool, bool); + impl_prop!(prop_u8, u8); + impl_prop!(prop_i8, i8); + impl_prop!(prop_u16, u16); + impl_prop!(prop_i16, i16); + impl_prop!(prop_u32, u32); + impl_prop!(prop_i32, i32); + impl_prop!(prop_u64, u64); + impl_prop!(prop_i64, i64); + impl_prop!(prop_f32, f32); + impl_prop!(prop_f64, f64); + + #[test] + fn fuzz_bool() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_bool as fn(bool)); } + #[test] + fn fuzz_u8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_u8 as fn(u8)); } + #[test] + fn fuzz_i8() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_i8 as fn(i8)); } + #[test] + fn fuzz_u16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_u16 as fn(u16)); } + #[test] + fn fuzz_i16() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_i16 as fn(i16)); } + #[test] + fn fuzz_u32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_u32 as fn(u32)); } + #[test] + fn fuzz_i32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_i32 as fn(i32)); } + #[test] + fn fuzz_u64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_u64 as fn(u64)); } + #[test] + fn fuzz_i64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_i64 as fn(i64)); } + #[test] + fn fuzz_f32() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_f32 as fn(f32)); } + #[test] + fn fuzz_f64() { quickcheck::QuickCheck::new().max_tests(N).quickcheck(prop_f64 as fn(f64)); } +} + + +#[cfg(test)] +mod write_and_read_examples { + extern crate flatbuffers; + + use super::create_serialized_example_with_library_code; + use super::create_serialized_example_with_generated_code; + use super::serialized_example_is_accessible_and_correct; + + #[test] + fn generated_code_creates_correct_example() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(b); + let buf = b.finished_data(); + serialized_example_is_accessible_and_correct(&buf[..], true, false).unwrap(); + } + + #[test] + fn generated_code_debug_prints_correctly() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(b); + let buf = b.finished_data(); + serialized_example_is_accessible_and_correct(&buf, true, false).unwrap(); + let m = super::my_game::example::root_as_monster(buf).unwrap(); + assert_eq!( + format!("{:.5?}", &m), + "Monster { pos: Some(Vec3 { x: 1.00000, y: 2.00000, z: 3.00000, \ + test1: 3.00000, test2: Color(Green), test3: Test { a: 5, b: 6 } \ + }), mana: 150, hp: 80, name: \"MyMonster\", inventory: Some([0, 1, \ + 2, 3, 4]), color: Color(Blue), test_type: Monster, test: Monster { \ + pos: None, mana: 150, hp: 100, name: \"Fred\", inventory: None, \ + color: Color(Blue), test_type: NONE, test: None, test4: None, \ + testarrayofstring: None, testarrayoftables: None, enemy: None, \ + testnestedflatbuffer: None, testempty: None, testbool: false, \ + testhashs32_fnv1: 0, testhashu32_fnv1: 0, testhashs64_fnv1: 0, \ + testhashu64_fnv1: 0, testhashs32_fnv1a: 0, testhashu32_fnv1a: 0, \ + testhashs64_fnv1a: 0, testhashu64_fnv1a: 0, testarrayofbools: \ + None, testf: 3.14159, testf2: 3.00000, testf3: 0.00000, \ + testarrayofstring2: None, testarrayofsortedstruct: None, flex: \ + None, test5: None, vector_of_longs: None, vector_of_doubles: None, \ + parent_namespace_test: None, vector_of_referrables: None, \ + single_weak_reference: 0, vector_of_weak_references: None, \ + vector_of_strong_referrables: None, co_owning_reference: 0, \ + vector_of_co_owning_references: None, non_owning_reference: 0, \ + vector_of_non_owning_references: None, any_unique_type: NONE, \ + any_unique: None, any_ambiguous_type: NONE, any_ambiguous: None, \ + vector_of_enums: None, signed_enum: None, \ + testrequirednestedflatbuffer: None, scalar_key_sorted_tables: \ + None, native_inline: None, long_enum_non_enum_default: \ + LongEnum(0x0), long_enum_normal_default: LongEnum(LongOne), \ + nan_default: NaN, inf_default: inf, positive_inf_default: inf, \ + infinity_default: inf, positive_infinity_default: inf, \ + negative_inf_default: -inf, negative_infinity_default: -inf, \ + double_inf_default: inf }, test4: Some([Test { a: 10, b: 20 }, \ + Test { a: 30, b: 40 }]), testarrayofstring: Some([\"test1\", \ + \"test2\"]), testarrayoftables: None, enemy: None, \ + testnestedflatbuffer: None, testempty: None, testbool: false, \ + testhashs32_fnv1: 0, testhashu32_fnv1: 0, testhashs64_fnv1: 0, \ + testhashu64_fnv1: 0, testhashs32_fnv1a: 0, testhashu32_fnv1a: 0, \ + testhashs64_fnv1a: 0, testhashu64_fnv1a: 0, testarrayofbools: \ + None, testf: 3.14159, testf2: 3.00000, testf3: 0.00000, \ + testarrayofstring2: None, testarrayofsortedstruct: None, flex: \ + None, test5: None, vector_of_longs: None, vector_of_doubles: None, \ + parent_namespace_test: None, vector_of_referrables: None, \ + single_weak_reference: 0, vector_of_weak_references: None, \ + vector_of_strong_referrables: None, co_owning_reference: 0, \ + vector_of_co_owning_references: None, non_owning_reference: 0, \ + vector_of_non_owning_references: None, any_unique_type: NONE, \ + any_unique: None, any_ambiguous_type: NONE, any_ambiguous: None, \ + vector_of_enums: None, signed_enum: None, \ + testrequirednestedflatbuffer: None, scalar_key_sorted_tables: \ + None, native_inline: None, long_enum_non_enum_default: \ + LongEnum(0x0), long_enum_normal_default: LongEnum(LongOne), \ + nan_default: NaN, inf_default: inf, positive_inf_default: inf, \ + infinity_default: inf, positive_infinity_default: inf, \ + negative_inf_default: -inf, negative_infinity_default: -inf, \ + double_inf_default: inf }" + ); + } + + #[test] + #[cfg(not(miri))] // slow. + fn generated_code_creates_correct_example_repeatedly_with_reset() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + for _ in 0..100 { + create_serialized_example_with_generated_code(b); + { + let buf = b.finished_data(); + serialized_example_is_accessible_and_correct(&buf[..], true, false).unwrap(); + } + b.reset(); + } + } + + #[test] + fn library_code_creates_correct_example() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_library_code(b); + let buf = b.finished_data(); + serialized_example_is_accessible_and_correct(&buf[..], true, false).unwrap(); + } + + #[test] + #[cfg(not(miri))] // slow. + fn library_code_creates_correct_example_repeatedly_with_reset() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + for _ in 0..100 { + create_serialized_example_with_library_code(b); + { + let buf = b.finished_data(); + serialized_example_is_accessible_and_correct(&buf[..], true, false).unwrap(); + } + b.reset(); + } + } +} + +#[cfg(not(feature = "no_std"))] +#[cfg(test)] +mod read_examples_from_other_language_ports { + extern crate flatbuffers; + + use std::println; + + use super::load_file; + use super::serialized_example_is_accessible_and_correct; + + #[test] + fn gold_cpp_example_data_is_accessible_and_correct() { + let buf = load_file("../monsterdata_test.mon").expect("missing monsterdata_test.mon"); + serialized_example_is_accessible_and_correct(&buf[..], true, false).unwrap(); + } + #[test] + fn java_wire_example_data_is_accessible_and_correct() { + let buf = load_file("../monsterdata_java_wire.mon"); + if buf.is_err() { + println!("skipping java wire test because it is not present"); + return; + } + let buf = buf.unwrap(); + serialized_example_is_accessible_and_correct(&buf[..], true, false).unwrap(); + } + #[test] + fn java_wire_size_prefixed_example_data_is_accessible_and_correct() { + let buf = load_file("../monsterdata_java_wire_sp.mon"); + if buf.is_err() { + println!("skipping java wire test because it is not present"); + return; + } + let buf = buf.unwrap(); + serialized_example_is_accessible_and_correct(&buf[..], true, true).unwrap(); + } +} + +#[cfg(test)] +mod generated_code_asserts { + extern crate flatbuffers; + + use super::my_game; + + #[test] + #[should_panic] + fn monster_builder_fails_when_name_is_missing() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + my_game::example::Monster::create(b, &my_game::example::MonsterArgs{..Default::default()}); + } +} + +#[cfg(test)] +mod generated_key_comparisons { + extern crate flatbuffers; + + use super::my_game; + + #[test] + fn struct_ability_key_compare_less_than() { + let a = my_game::example::Ability::new(1, 2); + let b = my_game::example::Ability::new(2, 1); + let c = my_game::example::Ability::new(3, 3); + + assert_eq!(a.key_compare_less_than(&a), false); + assert_eq!(b.key_compare_less_than(&b), false); + assert_eq!(c.key_compare_less_than(&c), false); + + assert_eq!(a.key_compare_less_than(&b), true); + assert_eq!(a.key_compare_less_than(&c), true); + + assert_eq!(b.key_compare_less_than(&a), false); + assert_eq!(b.key_compare_less_than(&c), true); + + assert_eq!(c.key_compare_less_than(&a), false); + assert_eq!(c.key_compare_less_than(&b), false); + } + + #[test] + fn struct_key_compare_with_value() { + let a = my_game::example::Ability::new(1, 2); + + assert_eq!(a.key_compare_with_value(0), ::core::cmp::Ordering::Greater); + assert_eq!(a.key_compare_with_value(1), ::core::cmp::Ordering::Equal); + assert_eq!(a.key_compare_with_value(2), ::core::cmp::Ordering::Less); + } + + #[test] + fn struct_key_compare_less_than() { + let a = my_game::example::Ability::new(1, 2); + let b = my_game::example::Ability::new(2, 1); + let c = my_game::example::Ability::new(3, 3); + + assert_eq!(a.key_compare_less_than(&a), false); + assert_eq!(b.key_compare_less_than(&b), false); + assert_eq!(c.key_compare_less_than(&c), false); + + assert_eq!(a.key_compare_less_than(&b), true); + assert_eq!(a.key_compare_less_than(&c), true); + + assert_eq!(b.key_compare_less_than(&a), false); + assert_eq!(b.key_compare_less_than(&c), true); + + assert_eq!(c.key_compare_less_than(&a), false); + assert_eq!(c.key_compare_less_than(&b), false); + } + + #[test] + fn table_key_compare_with_value() { + // setup + let builder = &mut flatbuffers::FlatBufferBuilder::new(); + super::create_serialized_example_with_library_code(builder); + let buf = builder.finished_data(); + let a = my_game::example::root_as_monster(buf).unwrap(); + + // preconditions + assert_eq!(a.name(), "MyMonster"); + + assert_eq!(a.key_compare_with_value("AAA"), ::core::cmp::Ordering::Greater); + assert_eq!(a.key_compare_with_value("MyMonster"), ::core::cmp::Ordering::Equal); + assert_eq!(a.key_compare_with_value("ZZZ"), ::core::cmp::Ordering::Less); + } + + #[test] + fn table_key_compare_less_than() { + // setup + let builder = &mut flatbuffers::FlatBufferBuilder::new(); + super::create_serialized_example_with_library_code(builder); + let buf = builder.finished_data(); + let a = my_game::example::root_as_monster(buf).unwrap(); + let b = a.test_as_monster().unwrap(); + + // preconditions + assert_eq!(a.name(), "MyMonster"); + assert_eq!(b.name(), "Fred"); + + assert_eq!(a.key_compare_less_than(&a), false); + assert_eq!(a.key_compare_less_than(&b), false); + + assert_eq!(b.key_compare_less_than(&a), true); + assert_eq!(b.key_compare_less_than(&b), false); + } +} + +#[cfg(test)] +mod included_schema_generated_code { + + #[test] + #[allow(unused_imports)] + fn namespace_test_mod_is_importable() { + use super::namespace_test_generated::{ + namespace_a, + namespace_a::namespace_b, + namespace_c, + }; + + } +} + +#[cfg(test)] +mod builder_asserts { + extern crate flatbuffers; + + #[test] + #[should_panic] + fn end_table_should_panic_when_not_in_table() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.end_table(flatbuffers::WIPOffset::new(0)); + } + + #[test] + #[should_panic] + fn create_string_should_panic_when_in_table() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_table(); + b.create_string("foo"); + } + + #[test] + #[should_panic] + fn create_byte_string_should_panic_when_in_table() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_table(); + b.create_byte_string(b"foo"); + } + + #[test] + #[should_panic] + fn push_struct_slot_should_panic_when_not_in_table() { + #[derive(Copy, Clone, Debug, PartialEq)] + #[repr(C, packed)] + struct foo { } + impl<'b> flatbuffers::Push for &'b foo { + type Output = foo; + unsafe fn push<'a>(&'a self, _dst: &'a mut [u8], _written_len: usize) { } + } + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push_slot_always(0, &foo{}); + } + + #[test] + #[should_panic] + fn finished_bytes_should_panic_when_table_is_not_finished() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_table(); + b.finished_data(); + } + + #[test] + #[should_panic] + fn required_panics_when_field_not_set() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let start = b.start_table(); + let o = b.end_table(start); + b.required(o, 4 /* byte offset to first field */, "test field"); + } +} + +#[cfg(test)] +mod follow_impls { + extern crate flatbuffers; + use flatbuffers::Follow; + use flatbuffers::field_index_to_field_offset as fi2fo; + + use alloc::vec::Vec; + + // Define a test struct to use in a few tests. This replicates the work that the code generator + // would normally do when defining a FlatBuffer struct. For reference, compare the following + // `FooStruct` code with the code generated for the `Vec3` struct in + // `../../preserve_case_rust/monster_test/mod.rs`. + use flatbuffers::EndianScalar; + #[derive(Copy, Clone, Debug, PartialEq)] + #[repr(C, packed)] + struct FooStruct { + a: i8, + b: u8, + c: i16, + } + impl FooStruct { + fn new(_a: i8, _b: u8, _c: i16) -> Self { + FooStruct { + a: _a.to_little_endian(), + b: _b.to_little_endian(), + c: _c.to_little_endian(), + } + } + } + impl<'a> flatbuffers::Follow<'a> for FooStruct { + type Inner = &'a FooStruct; + #[inline(always)] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + <&'a FooStruct>::follow(buf, loc) + } + } + impl<'a> flatbuffers::Follow<'a> for &'a FooStruct { + type Inner = &'a FooStruct; + #[inline(always)] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + flatbuffers::follow_cast_ref::(buf, loc) + } + } + + #[test] + fn to_u8() { + let vec: Vec = vec![255, 3]; + let fs: flatbuffers::FollowStart = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { fs.self_follow(&vec[..], 1) }, 3); + } + + #[test] + fn to_u16() { + let vec: Vec = vec![255, 255, 3, 4]; + let fs: flatbuffers::FollowStart = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { fs.self_follow(&vec[..], 2) }, 1027); + } + + #[test] + fn to_f32() { + let vec: Vec = vec![255, 255, 255, 255, /* start of value */ 208, 15, 73, 64]; + let fs: flatbuffers::FollowStart = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { fs.self_follow(&vec[..], 4) }, 3.14159); + } + + #[test] + fn to_string() { + let vec: Vec = vec![255,255,255,255, 3, 0, 0, 0, 'f' as u8, 'o' as u8, 'o' as u8, 0]; + let off: flatbuffers::FollowStart<&str> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { off.self_follow(&vec[..], 4) }, "foo"); + } + + #[test] + fn to_byte_slice() { + let vec: Vec = vec![255, 255, 255, 255, 4, 0, 0, 0, 1, 2, 3, 4]; + let off: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { off.self_follow(&vec[..], 4).bytes() }, &[1, 2, 3, 4][..]); + } + + #[test] + fn to_vector_of_u16() { + let vec: Vec = vec![255, 255, 255, 255, 2, 0, 0, 0, 1, 2, 3, 4]; + let off: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { off.self_follow(&vec[..], 4).len() }, 2); + assert_eq!(unsafe { off.self_follow(&vec[..], 4).get(0) }, 513); + assert_eq!(unsafe { off.self_follow(&vec[..], 4).get(1) }, 1027); + } + + #[test] + fn to_struct() { + let vec: Vec = vec![255, 255, 255, 255, 1, 2, 3, 4]; + let off: flatbuffers::FollowStart<&FooStruct> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { *off.self_follow(&vec[..], 4) }, FooStruct::new(1, 2, 1027)); + } + + #[test] + fn to_vector_of_offset_to_string_elements() { + let buf: Vec = vec![/* vec len */ 1, 0, 0, 0, /* offset to string */ 4, 0, 0, 0, /* str length */ 3, 0, 0, 0, 'f' as u8, 'o' as u8, 'o' as u8, 0]; + let s: flatbuffers::FollowStart>> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe {s.self_follow(&buf[..], 0).len() }, 1); + assert_eq!(unsafe { s.self_follow(&buf[..], 0).get(0) }, "foo"); + } + + #[test] + fn to_vector_of_struct_elements() { + let buf: Vec = vec![1, 0, 0, 0, /* struct data */ 1, 2, 3, 4]; + let fs: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + assert_eq!(unsafe { fs.self_follow(&buf[..], 0).len() }, 1); + assert_eq!(unsafe { fs.self_follow(&buf[..], 0).get(0) }, &FooStruct::new(1, 2, 1027)); + } + + #[test] + fn to_root_to_empty_table() { + let buf: Vec = vec![ + 12, 0, 0, 0, // offset to root table + // enter vtable + 4, 0, // vtable len + 0, 0, // inline size + 255, 255, 255, 255, // canary + // enter table + 8, 0, 0, 0, // vtable location + ]; + unsafe { + let fs: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + assert_eq!(fs.self_follow(&buf[..], 0), flatbuffers::Table::new(&buf[..], 12)); + } + } + + #[test] + fn to_root_table_get_slot_scalar_u8() { + let buf: Vec = vec![ + 14, 0, 0, 0, // offset to root table + // enter vtable + 6, 0, // vtable len + 2, 0, // inline size + 5, 0, // value loc + 255, 255, 255, 255, // canary + // enter table + 10, 0, 0, 0, // vtable location + 0, 99 // value (with padding) + ]; + unsafe { + let fs: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + let tab = fs.self_follow(&buf[..], 0); + assert_eq!(tab.get::(fi2fo(0), Some(123)), Some(99)); + } + } + + #[test] + fn to_root_to_table_get_slot_scalar_u8_default_via_vtable_len() { + let buf: Vec = vec![ + 12, 0, 0, 0, // offset to root table + // enter vtable + 4, 0, // vtable len + 2, 0, // inline size + 255, 255, 255, 255, // canary + // enter table + 8, 0, 0, 0, // vtable location + ]; + unsafe { + let fs: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + let tab = fs.self_follow(&buf[..], 0); + assert_eq!(tab.get::(fi2fo(0), Some(123)), Some(123)); + } + } + + #[test] + fn to_root_to_table_get_slot_scalar_u8_default_via_vtable_zero() { + let buf: Vec = vec![ + 14, 0, 0, 0, // offset to root table + // enter vtable + 6, 0, // vtable len + 2, 0, // inline size + 0, 0, // zero means use the default value + 255, 255, 255, 255, // canary + // enter table + 10, 0, 0, 0, // vtable location + ]; + unsafe { + let fs: flatbuffers::FollowStart> = flatbuffers::FollowStart::new(); + let tab = fs.self_follow(&buf[..], 0); + assert_eq!(tab.get::(fi2fo(0), Some(123)), Some(123)); + } + } + + #[test] + fn to_root_to_table_get_slot_string_multiple_types() { + let buf: Vec = vec![ + 14, 0, 0, 0, // offset to root table + // enter vtable + 6, 0, // vtable len + 2, 0, // inline size + 4, 0, // value loc + 255, 255, 255, 255, // canary + // enter table + 10, 0, 0, 0, // vtable location + 8, 0, 0, 0, // offset to string + // leave table + 255, 255, 255, 255, // canary + // enter string + 3, 0, 0, 0, 109, 111, 111, 0 // string length and contents + ]; + unsafe { + let tab = >::follow(&buf[..], 0); + assert_eq!(tab.get::>(fi2fo(0), None), Some("moo")); + let byte_vec = tab.get::>>(fi2fo(0), None).unwrap().bytes(); + assert_eq!(byte_vec, &vec![109, 111, 111][..]); + let v = tab.get::>>(fi2fo(0), None).unwrap(); + assert_eq!(v.len(), 3); + assert_eq!(v.get(0), 109); + assert_eq!(v.get(1), 111); + assert_eq!(v.get(2), 111); + } + } + + #[test] + fn to_root_to_table_get_slot_string_multiple_types_default_via_vtable_len() { + let buf: Vec = vec![ + 12, 0, 0, 0, // offset to root table + // enter vtable + 4, 0, // vtable len + 4, 0, // table inline len + 255, 255, 255, 255, // canary + // enter table + 8, 0, 0, 0, // vtable location + ]; + + unsafe { + let tab = >::follow(&buf[..], 0); + assert_eq!(tab.get::>(fi2fo(0), Some("abc")), Some("abc")); + #[cfg(target_endian = "little")] + { + assert_eq!(tab.get::>(fi2fo(0), Some(&vec![70, 71, 72][..])), Some(&vec![70, 71, 72][..])); + } + + let default_vec_buf: Vec = vec![3, 0, 0, 0, 70, 71, 72, 0]; + let default_vec = flatbuffers::Vector::new(&default_vec_buf[..], 0); + let v = tab.get::>>(fi2fo(0), Some(default_vec)).unwrap(); + assert_eq!(v.len(), 3); + assert_eq!(v.get(0), 70); + assert_eq!(v.get(1), 71); + assert_eq!(v.get(2), 72); + } + } + + #[test] + fn to_root_to_table_get_slot_string_multiple_types_default_via_vtable_zero() { + let buf: Vec = vec![ + 14, 0, 0, 0, // offset to root table + // enter vtable + 6, 0, // vtable len + 2, 0, // inline size + 0, 0, // value loc + 255, 255, 255, 255, // canary + // enter table + 10, 0, 0, 0, // vtable location + ]; + unsafe { + let tab = >::follow(&buf[..], 0); + assert_eq!(tab.get::>(fi2fo(0), Some("abc")), Some("abc")); + #[cfg(target_endian = "little")] + { + assert_eq!(tab.get::>(fi2fo(0), Some(&vec![70, 71, 72][..])), Some(&vec![70, 71, 72][..])); + } + + let default_vec_buf: Vec = vec![3, 0, 0, 0, 70, 71, 72, 0]; + let default_vec = flatbuffers::Vector::new(&default_vec_buf[..], 0); + let v = tab.get::>>(fi2fo(0), Some(default_vec)).unwrap(); + assert_eq!(v.len(), 3); + assert_eq!(v.get(0), 70); + assert_eq!(v.get(1), 71); + assert_eq!(v.get(2), 72); + } + } +} + +#[cfg(test)] +mod push_impls { + extern crate flatbuffers; + + use super::my_game; + + fn check<'a>(b: &'a flatbuffers::FlatBufferBuilder, want: &'a [u8]) { + let got = b.unfinished_data(); + assert_eq!(want, got); + } + + #[test] + fn push_u8() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(123u8); + check(&b, &[123]); + } + + #[test] + fn push_u64() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(0x12345678); + check(&b, &[0x78, 0x56, 0x34, 0x12]); + } + + #[test] + fn push_f64() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(3.14159265359f64); + check(&b, &[234, 46, 68, 84, 251, 33, 9, 64]); + } + + #[test] + fn push_generated_struct() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(my_game::example::Test::new(10, 20)); + check(&b, &[10, 0, 20, 0]); + } + + #[test] + fn push_u8_vector_with_offset_with_alignment() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.create_vector(&[1u8, 2, 3, 4, 5, 6, 7, 8, 9][..]); + b.push(off); + check(&b, &[/* loc */ 4, 0, 0, 0, /* len */ 9, 0, 0, 0, /* val */ 1, 2, 3, 4, 5, 6, 7, 8, 9, /* padding */ 0, 0, 0]); + } + + #[test] + fn push_u8_u16_alignment() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(1u8); + b.push(2u16); + check(&b, &[2, 0, 0, 1]); + } + + #[test] + fn push_u8_u32_alignment() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(1u8); + b.push(2u32); + check(&b, &[2, 0, 0, 0, 0, 0, 0, 1]); + } + + #[test] + fn push_u8_u64_alignment() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(1u8); + b.push(2u64); + check(&b, &[2, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1]); + } + + #[test] + fn push_u8_generated_struct_alignment() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(1u8); + b.push(my_game::example::Test::new(10, 20)); + check(&b, &[10, 0, 20, 0, 0, 1]); + } +} + +#[cfg(test)] +mod vtable_deduplication { + extern crate flatbuffers; + use flatbuffers::field_index_to_field_offset as fi2fo; + + fn check<'a>(b: &'a flatbuffers::FlatBufferBuilder, want: &'a [u8]) { + let got = b.unfinished_data(); + assert_eq!(want, got); + } + + #[test] + fn one_empty_table() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let start0 = b.start_table(); + b.end_table(start0); + check(&b, &[ + 4, 0, // vtable size in bytes + 4, 0, // object inline data in bytes + + 4, 0, 0, 0, // backwards offset to vtable + ]); + } + + #[test] + fn two_empty_tables_are_deduplicated() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let start0 = b.start_table(); + b.end_table(start0); + let start1 = b.start_table(); + b.end_table(start1); + check(&b, &[ + 252, 255, 255, 255, // forwards offset to vtable + + 4, 0, // vtable size in bytes + 4, 0, // object inline data in bytes + + 4, 0, 0, 0, // backwards offset to vtable + ]); + } + + #[test] + fn two_tables_with_two_conveniently_sized_inline_elements_are_deduplicated() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let start0 = b.start_table(); + b.push_slot::(fi2fo(0), 100, 0); + b.push_slot::(fi2fo(1), 101, 0); + b.end_table(start0); + let start1 = b.start_table(); + b.push_slot::(fi2fo(0), 200, 0); + b.push_slot::(fi2fo(1), 201, 0); + b.end_table(start1); + check(&b, &[ + 240, 255, 255, 255, // forwards offset to vtable + + 201, 0, 0, 0, // value #1 + 200, 0, 0, 0, 0, 0, 0, 0, // value #0 + + 8, 0, // vtable size in bytes + 16, 0, // object inline data in bytes + 8, 0, // offset in object for value #0 + 4, 0, // offset in object for value #1 + + 8, 0, 0, 0, // backwards offset to vtable + 101, 0, 0, 0, // value #1 + 100, 0, 0, 0, 0, 0, 0, 0 // value #0 + ]); + } + + #[cfg(not(miri))] // slow. + #[test] + fn many_identical_tables_use_few_vtables() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + for _ in 0..1000 { + let start = b.start_table(); + b.push_slot::(fi2fo(0), 100, 0); + b.push_slot::(fi2fo(1), 101, 0); + b.end_table(start); + } + assert!(b.num_written_vtables() <= 10); + } +} + +#[cfg(test)] +mod byte_layouts { + extern crate flatbuffers; + use flatbuffers::field_index_to_field_offset as fi2fo; + + fn check<'a>(b: &'a flatbuffers::FlatBufferBuilder, want: &'a [u8]) { + let got = b.unfinished_data(); + assert_eq!(want, got); + } + + #[test] + fn layout_01_basic_numbers() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(true); + check(&b, &[1]); + b.push(-127i8); + check(&b, &[129, 1]); + b.push(255u8); + check(&b, &[255, 129, 1]); + b.push(-32222i16); + check(&b, &[0x22, 0x82, 0, 255, 129, 1]); // first pad + b.push(0xFEEEu16); + check(&b, &[0xEE, 0xFE, 0x22, 0x82, 0, 255, 129, 1]); // no pad this time + b.push(-53687092i32); + check(&b, &[204, 204, 204, 252, 0xEE, 0xFE, 0x22, 0x82, 0, 255, 129, 1]); + b.push(0x98765432u32); + check(&b, &[0x32, 0x54, 0x76, 0x98, 204, 204, 204, 252, 0xEE, 0xFE, 0x22, 0x82, 0, 255, 129, 1]); + } + + #[test] + fn layout_01b_bigger_numbers() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.push(0x1122334455667788u64); + check(&b, &[0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11]); + } + + #[test] + fn layout_02_1xbyte_vector() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + check(&b, &[]); + b.start_vector::(1); + check(&b, &[0, 0, 0]); // align to 4bytes + b.push(1u8); + check(&b, &[1, 0, 0, 0]); + b.end_vector::(1); + check(&b, &[1, 0, 0, 0, 1, 0, 0, 0]); // padding + } + + #[test] + fn layout_03_2xbyte_vector() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(2); + check(&b, &[0, 0]); // align to 4bytes + b.push(1u8); + check(&b, &[1, 0, 0]); + b.push(2u8); + check(&b, &[2, 1, 0, 0]); + b.end_vector::(2); + check(&b, &[2, 0, 0, 0, 2, 1, 0, 0]); // padding + } + + #[test] + fn layout_03b_11xbyte_vector_matches_builder_size() { + let mut b = flatbuffers::FlatBufferBuilder::with_capacity(12); + b.start_vector::(8); + + let mut gold = vec![0u8; 0]; + check(&b, &gold[..]); + + for i in 1u8..=8 { + b.push(i); + gold.insert(0, i); + check(&b, &gold[..]); + } + b.end_vector::(8); + let want = vec![8u8, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1]; + check(&b, &want[..]); + } + #[test] + fn layout_04_1xuint16_vector() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(1); + check(&b, &[0, 0]); // align to 4bytes + b.push(1u16); + check(&b, &[1, 0, 0, 0]); + b.end_vector::(1); + check(&b, &[1, 0, 0, 0, 1, 0, 0, 0]); // padding + } + + #[test] + fn layout_05_2xuint16_vector() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let _off = b.start_vector::(2); + check(&b, &[]); // align to 4bytes + b.push(0xABCDu16); + check(&b, &[0xCD, 0xAB]); + b.push(0xDCBAu16); + check(&b, &[0xBA, 0xDC, 0xCD, 0xAB]); + b.end_vector::(2); + check(&b, &[2, 0, 0, 0, 0xBA, 0xDC, 0xCD, 0xAB]); + } + + #[test] + fn layout_06_create_string() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off0 = b.create_string("foo"); + assert_eq!(8, off0.value()); + check(&b, b"\x03\x00\x00\x00foo\x00"); // 0-terminated, no pad + let off1 = b.create_string("moop"); + assert_eq!(20, off1.value()); + check(&b, b"\x04\x00\x00\x00moop\x00\x00\x00\x00\ + \x03\x00\x00\x00foo\x00"); // 0-terminated, 3-byte pad + } + + #[test] + fn layout_06b_create_string_unicode() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + // These characters are chinese from blog.golang.org/strings + // We use escape codes here so that editors without unicode support + // aren't bothered: + let uni_str = "\u{65e5}\u{672c}\u{8a9e}"; + let off0 = b.create_string(uni_str); + assert_eq!(16, off0.value()); + check(&b, &[9, 0, 0, 0, 230, 151, 165, 230, 156, 172, 232, 170, 158, 0, // null-terminated, 2-byte pad + 0, 0]); + } + + #[test] + fn layout_06c_create_byte_string() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off0 = b.create_byte_string(b"foo"); + assert_eq!(8, off0.value()); + check(&b, b"\x03\x00\x00\x00foo\x00"); // 0-terminated, no pad + let off1 = b.create_byte_string(b"moop"); + assert_eq!(20, off1.value()); + check(&b, b"\x04\x00\x00\x00moop\x00\x00\x00\x00\ + \x03\x00\x00\x00foo\x00"); // 0-terminated, 3-byte pad + } + + #[test] + fn layout_07_empty_vtable() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off0 = b.start_table(); + check(&b, &[]); + b.end_table(off0); + check(&b, &[4, 0, // vtable length + 4, 0, // length of table including vtable offset + 4, 0, 0, 0]); // offset for start of vtable + } + + #[test] + fn layout_08_vtable_with_one_true_bool() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + check(&b, &[]); + let off0 = b.start_table(); + assert_eq!(0, off0.value()); + check(&b, &[]); + b.push_slot(fi2fo(0), true, false); + check(&b, &[1]); + let off1 = b.end_table(off0); + assert_eq!(8, off1.value()); + check(&b, &[ + 6, 0, // vtable bytes + 8, 0, // length of object including vtable offset + 7, 0, // start of bool value + 6, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, 0, // padded to 4 bytes + 1, // bool value + ]); + } + + #[test] + fn layout_09_vtable_with_one_default_bool() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + check(&b, &[]); + let off = b.start_table(); + check(&b, &[]); + b.push_slot(fi2fo(0), false, false); + b.end_table(off); + check(&b, &[ + 4, 0, // vtable bytes + 4, 0, // end of object from here + // entry 1 is zero and not stored. + 4, 0, 0, 0, // offset for start of vtable (int32) + ]); + } + + #[test] + fn layout_09b_vtable_with_one_default_bool_force_defaults() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + check(&b, &[]); + let off = b.start_table(); + check(&b, &[]); + b.force_defaults(true); + b.push_slot(fi2fo(0), false, false); + b.end_table(off); + check(&b, &[ + 6, 0, // vtable bytes + 8, 0, // length of object including vtable offset + 7, 0, // start of bool value + 6, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, 0, // padded to 4 bytes + 0, // bool value + ]); + } + + #[test] + fn layout_10_vtable_with_one_int16() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + check(&b, &[]); + let off = b.start_table(); + b.push_slot(fi2fo(0), 0x789Ai16, 0); + b.end_table(off); + check(&b, &[ + 6, 0, // vtable bytes + 8, 0, // end of object from here + 6, 0, // offset to value + 6, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, // padding to 4 bytes + 0x9A, 0x78, + ]); + } + + #[test] + fn layout_11_vtable_with_two_int16() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot(fi2fo(0), 0x3456i16, 0); + b.push_slot(fi2fo(1), 0x789Ai16, 0); + b.end_table(off); + check(&b, &[ + 8, 0, // vtable bytes + 8, 0, // end of object from here + 6, 0, // offset to value 0 + 4, 0, // offset to value 1 + 8, 0, 0, 0, // offset for start of vtable (int32) + 0x9A, 0x78, // value 1 + 0x56, 0x34, // value 0 + ]); + } + + #[test] + fn layout_12_vtable_with_int16_and_bool() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot(fi2fo(0), 0x3456i16, 0); + b.push_slot(fi2fo(1), true, false); + b.end_table(off); + check(&b, &[ + 8, 0, // vtable bytes + 8, 0, // end of object from here + 6, 0, // offset to value 0 + 5, 0, // offset to value 1 + 8, 0, 0, 0, // offset for start of vtable (int32) + 0, // padding + 1, // value 1 + 0x56, 0x34, // value 0 + ]); + } + + #[test] + fn layout_12b_vtable_with_empty_vector() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(0); + let vecend = b.end_vector::(0); + let off = b.start_table(); + b.push_slot_always(fi2fo(0), vecend); + b.end_table(off); + check(&b, &[ + 6, 0, // vtable bytes + 8, 0, + 4, 0, // offset to vector offset + 6, 0, 0, 0, // offset for start of vtable (int32) + 4, 0, 0, 0, + 0, 0, 0, 0, // length of vector (not in struct) + ]); + } + + #[test] + fn layout_12c_vtable_with_empty_vector_of_byte_and_some_scalars() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(0); + let vecend = b.end_vector::(0); + let off = b.start_table(); + b.push_slot::(fi2fo(0), 55i16, 0); + b.push_slot_always::>(fi2fo(1), vecend); + b.end_table(off); + check(&b, &[ + 8, 0, // vtable bytes + 12, 0, + 10, 0, // offset to value 0 + 4, 0, // offset to vector offset + 8, 0, 0, 0, // vtable loc + 8, 0, 0, 0, // value 1 + 0, 0, 55, 0, // value 0 + + 0, 0, 0, 0, // length of vector (not in struct) + ]); + } + #[test] + fn layout_13_vtable_with_1_int16_and_2_vector_of_i16() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(2); + b.push(0x1234i16); + b.push(0x5678i16); + let vecend = b.end_vector::(2); + let off = b.start_table(); + b.push_slot_always(fi2fo(1), vecend); + b.push_slot(fi2fo(0), 55i16, 0); + b.end_table(off); + check(&b, &[ + 8, 0, // vtable bytes + 12, 0, // length of object + 6, 0, // start of value 0 from end of vtable + 8, 0, // start of value 1 from end of buffer + 8, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, // padding + 55, 0, // value 0 + 4, 0, 0, 0, // vector position from here + 2, 0, 0, 0, // length of vector (uint32) + 0x78, 0x56, // vector value 1 + 0x34, 0x12, // vector value 0 + ]); + } + #[test] + fn layout_14_vtable_with_1_struct_of_int8_and_int16_and_int32() { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + #[repr(C, packed)] + struct foo { + a: i32, + _pad0: [u8; 2], + b: i16, + _pad1: [u8; 3], + c: i8, + _pad2: [u8; 4], + } + assert_eq!(::core::mem::size_of::(), 16); + impl<'b> flatbuffers::Push for &'b foo { + type Output = foo; + unsafe fn push<'a>(&'a self, dst: &'a mut [u8], _written_len: usize) { + let src = ::core::slice::from_raw_parts(*self as *const foo as *const u8, ::core::mem::size_of::()); + dst.copy_from_slice(src); + } + } + + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + let x = foo{a: 0x12345678i32.to_le(), _pad0: [0,0], b: 0x1234i16.to_le(), _pad1: [0, 0, 0], c: 0x12i8.to_le(), _pad2: [0, 0, 0, 0]}; + b.push_slot_always(fi2fo(0), &x); + b.end_table(off); + check(&b, &[ + 6, 0, // vtable bytes + 20, 0, // end of object from here + 4, 0, // start of struct from here + 6, 0, 0, 0, // offset for start of vtable (int32) + + 0x78, 0x56, 0x34, 0x12, // value a + 0, 0, // padding + 0x34, 0x12, // value b + 0, 0, 0, // padding + 0x12, // value c + 0, 0, 0, 0, // padding + ]); + } + #[test] + fn layout_15_vtable_with_1_vector_of_4_int8() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + b.start_vector::(4); + b.push(33i8); + b.push(44i8); + b.push(55i8); + b.push(66i8); + let vecend = b.end_vector::(4); + let off = b.start_table(); + b.push_slot_always(fi2fo(0), vecend); + b.end_table(off); + check(&b, &[ + 6, 0, // vtable bytes + 8, 0, + 4, 0, // offset of vector offset + 6, 0, 0, 0, // offset for start of vtable (int32) + 4, 0, 0, 0, // vector start offset + + 4, 0, 0, 0, // vector length + 66, // vector value 1,1 + 55, // vector value 1,0 + 44, // vector value 0,1 + 33, // vector value 0,0 + ]); + } + + #[test] + fn layout_16_table_with_some_elements() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot(fi2fo(0), 33i8, 0); + b.push_slot(fi2fo(1), 66i16, 0); + let off2 = b.end_table(off); + b.finish_minimal(off2); + + check(&b, &[ + 12, 0, 0, 0, // root of table: points to vtable offset + + 8, 0, // vtable bytes + 8, 0, // end of object from here + 7, 0, // start of value 0 + 4, 0, // start of value 1 + + 8, 0, 0, 0, // offset for start of vtable (int32) + + 66, 0, // value 1 + 0, // padding + 33, // value 0 + ]); + } + + #[test] + fn layout_17_one_unfinished_table_and_one_finished_table() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + { + let off = b.start_table(); + b.push_slot(fi2fo(0), 33i8, 0); + b.push_slot(fi2fo(1), 44i8, 0); + b.end_table(off); + } + + { + let off = b.start_table(); + b.push_slot(fi2fo(0), 55i8, 0); + b.push_slot(fi2fo(1), 66i8, 0); + b.push_slot(fi2fo(2), 77i8, 0); + let off2 = b.end_table(off); + b.finish_minimal(off2); + } + + check(&b, &[ + 16, 0, 0, 0, // root of table: points to object + 0, 0, // padding + + 10, 0, // vtable bytes + 8, 0, // size of object + 7, 0, // start of value 0 + 6, 0, // start of value 1 + 5, 0, // start of value 2 + 10, 0, 0, 0, // offset for start of vtable (int32) + 0, // padding + 77, // value 2 + 66, // value 1 + 55, // value 0 + + //12, 0, 0, 0, // root of table: points to object + + 8, 0, // vtable bytes + 8, 0, // size of object + 7, 0, // start of value 0 + 6, 0, // start of value 1 + 8, 0, 0, 0, // offset for start of vtable (int32) + 0, 0, // padding + 44, // value 1 + 33, // value 0 + ]); + } + + #[test] + fn layout_18_a_bunch_of_bools() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot(fi2fo(0), true, false); + b.push_slot(fi2fo(1), true, false); + b.push_slot(fi2fo(2), true, false); + b.push_slot(fi2fo(3), true, false); + b.push_slot(fi2fo(4), true, false); + b.push_slot(fi2fo(5), true, false); + b.push_slot(fi2fo(6), true, false); + b.push_slot(fi2fo(7), true, false); + let off2 = b.end_table(off); + b.finish_minimal(off2); + + check(&b, &[ + 24, 0, 0, 0, // root of table: points to vtable offset + + 20, 0, // vtable bytes + 12, 0, // size of object + 11, 0, // start of value 0 + 10, 0, // start of value 1 + 9, 0, // start of value 2 + 8, 0, // start of value 3 + 7, 0, // start of value 4 + 6, 0, // start of value 5 + 5, 0, // start of value 6 + 4, 0, // start of value 7 + 20, 0, 0, 0, // vtable offset + + 1, // value 7 + 1, // value 6 + 1, // value 5 + 1, // value 4 + 1, // value 3 + 1, // value 2 + 1, // value 1 + 1, // value 0 + ]); + } + + #[test] + fn layout_19_three_bools() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot(fi2fo(0), true, false); + b.push_slot(fi2fo(1), true, false); + b.push_slot(fi2fo(2), true, false); + let off2 = b.end_table(off); + b.finish_minimal(off2); + + check(&b, &[ + 16, 0, 0, 0, // root of table: points to vtable offset + + 0, 0, // padding + + 10, 0, // vtable bytes + 8, 0, // size of object + 7, 0, // start of value 0 + 6, 0, // start of value 1 + 5, 0, // start of value 2 + 10, 0, 0, 0, // vtable offset from here + + 0, // padding + 1, // value 2 + 1, // value 1 + 1, // value 0 + ]); + } + + #[test] + fn layout_20_some_floats() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot(fi2fo(0), 1.0f32, 0.0); + b.end_table(off); + + check(&b, &[ + 6, 0, // vtable bytes + 8, 0, // size of object + 4, 0, // start of value 0 + 6, 0, 0, 0, // vtable offset + + 0, 0, 128, 63, // value 0 + ]); + } + + #[test] + fn layout_21_vtable_defaults() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot::(fi2fo(0), 1, 1); + b.push_slot::(fi2fo(1), 3, 2); + b.push_slot::(fi2fo(2), 3, 3); + b.end_table(off); + check(&b, &[ + 8, 0, // vtable size in bytes + 8, 0, // object inline data in bytes + 0, 0, // entry 1/3: 0 => default + 7, 0, // entry 2/3: 7 => table start + 7 bytes + // entry 3/3: not present => default + 8, 0, 0, 0, + 0, 0, 0, + 3, + ]); + } + + #[test] + fn layout_22_root() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + // skipped: b.push_slot_scalar::(0, 1, 1); + b.push_slot::(fi2fo(1), 3, 2); + b.push_slot::(fi2fo(2), 3, 3); + let table_end = b.end_table(off); + b.finish_minimal(table_end); + check(&b, &[ + 12, 0, 0, 0, // root + + 8, 0, // vtable size in bytes + 8, 0, // object inline data in bytes + 0, 0, // entry 1/3: 0 => default + 6, 0, // entry 2/3: 6 => table start + 6 bytes + // entry 3/3: not present => default + 8, 0, 0, 0, // size of table data in bytes + 0, 0, // padding + 3, 0, // value 2/3 + ]); + } + #[test] + fn layout_23_varied_slots_and_root() { + let mut b = flatbuffers::FlatBufferBuilder::new(); + let off = b.start_table(); + b.push_slot::(fi2fo(0), 1, 0); + b.push_slot::(fi2fo(1), 2, 0); + b.push_slot::(fi2fo(2), 3.0, 0.0); + let table_end = b.end_table(off); + b.finish_minimal(table_end); + check(&b, &[ + 16, 0, 0, 0, // root + 0, 0, // padding + 10, 0, // vtable bytes + 12, 0, // object inline data size + 10, 0, // offset to value #1 (i16) + 9, 0, // offset to value #2 (u8) + 4, 0, // offset to value #3 (f32) + 10, 0, // offset to vtable + 0, 0, // padding + 0, 0, 64, 64, // value #3 => 3.0 (float32) + 0, 2, // value #1 => 2 (u8) + 1, 0, // value #0 => 1 (int16) + ]); + } +} + +#[cfg(test)] +mod copy_clone_traits { + + use alloc::vec::Vec; + + #[test] + fn follow_types_implement_copy_and_clone() { + static_assertions::assert_impl_all!(flatbuffers::WIPOffset: Copy, Clone); + static_assertions::assert_impl_all!(flatbuffers::WIPOffset>: Copy, Clone); + + static_assertions::assert_impl_all!(flatbuffers::ForwardsUOffset: Copy, Clone); + static_assertions::assert_impl_all!(flatbuffers::ForwardsUOffset>: Copy, Clone); + + static_assertions::assert_impl_all!(flatbuffers::Vector<'static, u32>: Copy, Clone); + static_assertions::assert_impl_all!(flatbuffers::Vector<'static, Vec>: Copy, Clone); + } +} + +#[cfg(test)] +mod fully_qualified_name { + #[test] + fn fully_qualified_name_generated() { + assert!(check_eq!(super::my_game::example::Monster::get_fully_qualified_name(), "MyGame.Example.Monster").is_ok()); + assert!(check_eq!(super::my_game::example_2::Monster::get_fully_qualified_name(), "MyGame.Example2.Monster").is_ok()); + + assert!(check_eq!(super::my_game::example::Vec3::get_fully_qualified_name(), "MyGame.Example.Vec3").is_ok()); + assert!(check_eq!(super::my_game::example::Ability::get_fully_qualified_name(), "MyGame.Example.Ability").is_ok()); + } +} + +// this is not technically a test, but we want to always keep this generated +// file up-to-date, and the simplest way to do that is to make sure that when +// tests are run, the file is generated. +#[cfg(not(feature = "no_std"))] +#[test] +fn write_example_wire_data_to_file() { + let b = &mut flatbuffers::FlatBufferBuilder::new(); + create_serialized_example_with_generated_code(b); + + use ::std::io::Write; + let mut f = std::fs::File::create("../monsterdata_rust_wire.mon").unwrap(); + f.write_all(b.finished_data()).unwrap(); +} + +#[cfg(not(feature = "no_std"))] +fn load_file(filename: &str) -> Result, std::io::Error> { + use std::io::Read; + let mut f = std::fs::File::open(filename)?; + let mut buf = Vec::new(); + f.read_to_end(&mut buf)?; + Ok(buf) +} + +#[test] +fn test_shared_strings() { + let mut builder = flatbuffers::FlatBufferBuilder::new(); + let offset1 = builder.create_shared_string("welcome to flatbuffers!!"); + let offset2 = builder.create_shared_string("welcome"); + let offset3 = builder.create_shared_string("welcome to flatbuffers!!"); + assert_ne!(offset2.value(), offset3.value()); + assert_eq!(offset1.value(), offset3.value()); + builder.reset(); + let offset4 = builder.create_shared_string("welcome"); + let offset5 = builder.create_shared_string("welcome to flatbuffers!!"); + assert_ne!(offset2.value(), offset4.value()); + assert_ne!(offset5.value(), offset1.value()); + builder.reset(); + + // Checks if the shared string function would always work with + // an object in between the writes + let name = builder.create_shared_string("foo"); + let enemy = my_game::example::Monster::create(&mut builder, &my_game::example::MonsterArgs { + name: Some(name), + ..Default::default() + }); + let secondary_name = builder.create_shared_string("foo"); + assert_eq!(name.value(), secondary_name.value()); + + // Builds a new monster object and embeds enemy into it so we can verify + // that shared strings are working. + let args = my_game::example::MonsterArgs { + name: Some(secondary_name), + enemy: Some(enemy), + testarrayofstring: Some(builder.create_vector(&[name, secondary_name])), + ..Default::default() + }; + // Building secondary monster + let main_monster = my_game::example::Monster::create(&mut builder, &args); + builder.finish(main_monster, None); + let monster = my_game::example::root_as_monster(builder.finished_data()).unwrap(); + + // Checks if the embedded object (Enemy) name is foo + assert_eq!(monster.enemy().unwrap().name(), "foo"); + let string_vector = monster.testarrayofstring().unwrap(); + // Check if the vector will have the same string + assert_eq!(string_vector.get(0), "foo"); + assert_eq!(string_vector.get(1), "foo"); +} + +} diff --git a/tests/rust_usage_test/tests/more_defaults_test_preserve_case.rs b/tests/rust_usage_test/tests/more_defaults_test_preserve_case.rs new file mode 100644 index 00000000000..4d62a4a3f84 --- /dev/null +++ b/tests/rust_usage_test/tests/more_defaults_test_preserve_case.rs @@ -0,0 +1,35 @@ +extern crate alloc; + +use alloc::string::ToString; +use alloc::vec::Vec; + +#[allow(dead_code, unused_imports)] +#[path = "../../more_defaults/mod.rs"] +mod more_defaults_generated; +use self::more_defaults_generated::*; + +#[test] +fn object_defaults() { + assert_eq!( + MoreDefaultsT::default(), + MoreDefaultsT { + ints: Vec::new(), + floats: Vec::new(), + empty_string: "".to_string(), + some_string: "some".to_string(), + abcs: Vec::new(), + bools: Vec::new(), + }, + ) +} + +#[test] +fn nonpresent_values() { + let m = flatbuffers::root::(&[0; 4]).unwrap(); + assert_eq!(m.ints().len(), 0); + assert_eq!(m.floats().len(), 0); + assert_eq!(m.abcs().len(), 0); + assert_eq!(m.bools().len(), 0); + assert_eq!(m.empty_string(), ""); + assert_eq!(m.some_string(), "some"); +} diff --git a/tests/rust_usage_test/tests/optional_scalars_test_preserve_case.rs b/tests/rust_usage_test/tests/optional_scalars_test_preserve_case.rs new file mode 100644 index 00000000000..9807c13aeeb --- /dev/null +++ b/tests/rust_usage_test/tests/optional_scalars_test_preserve_case.rs @@ -0,0 +1,124 @@ +#[allow(dead_code, unused_imports)] +#[path = "../../preserve_case_rust/optional_scalars/mod.rs"] +mod optional_scalars_generated; +use crate::optional_scalars_generated::optional_scalars::*; + +// There are 3 variants of scalars in tables - those specified with default=42, +// optional scalars, and those with nothing specified (implicitly default=0). +// This tests that you can read what you write. +macro_rules! make_test { + ( + $test_name: ident, + $just: ident, $default: ident, $maybe: ident, + $five: expr, $zero: expr, $fortytwo: expr + ) => { + #[test] + fn $test_name() { + let mut builder = flatbuffers::FlatBufferBuilder::new(); + // Test five makes sense when specified. + let ss = ScalarStuff::create( + &mut builder, + &ScalarStuffArgs { + $just: $five, + $default: $five, + $maybe: Some($five), + ..Default::default() + }, + ); + builder.finish(ss, None); + + let s = flatbuffers::root::(builder.finished_data()).unwrap(); + assert_eq!(s.$just(), $five); + assert_eq!(s.$default(), $five); + assert_eq!(s.$maybe(), Some($five)); + + // Test defaults are used when not specified. + let s = flatbuffers::root::(&[0; 8]).unwrap(); + assert_eq!(s.$just(), $zero); + assert_eq!(s.$default(), $fortytwo); + assert_eq!(s.$maybe(), None); + + // Same for object API + let s = flatbuffers::root::(builder.finished_data()).unwrap().unpack(); + assert_eq!(s.$just, $five); + assert_eq!(s.$default, $five); + assert_eq!(s.$maybe, Some($five)); + let s = flatbuffers::root::(&[0; 8]).unwrap().unpack(); + assert_eq!(s.$just, $zero); + assert_eq!(s.$default, $fortytwo); + assert_eq!(s.$maybe, None); + } + }; +} + +make_test!(optional_i8, just_i8, default_i8, maybe_i8, 5, 0, 42); +make_test!(optional_u8, just_u8, default_u8, maybe_u8, 5, 0, 42); +make_test!(optional_i16, just_i16, default_i16, maybe_i16, 5, 0, 42); +make_test!(optional_u16, just_u16, default_u16, maybe_u16, 5, 0, 42); +make_test!(optional_i32, just_i32, default_i32, maybe_i32, 5, 0, 42); +make_test!(optional_u32, just_u32, default_u32, maybe_u32, 5, 0, 42); +make_test!(optional_i64, just_i64, default_i64, maybe_i64, 5, 0, 42); +make_test!(optional_u64, just_u64, default_u64, maybe_u64, 5, 0, 42); +make_test!(optional_f32, just_f32, default_f32, maybe_f32, 5.0, 0.0, 42.0); +make_test!(optional_f64, just_f64, default_f64, maybe_f64, 5.0, 0.0, 42.0); +make_test!(optional_bool, just_bool, default_bool, maybe_bool, true, false, true); +make_test!( + optional_enum, + just_enum, + default_enum, + maybe_enum, + OptionalByte::Two, + OptionalByte::None, + OptionalByte::One +); + +#[test] +fn object_api_defaults() { + assert_eq!( + ScalarStuffT::default(), + ScalarStuffT { + just_i8: 0, + maybe_i8: None, + default_i8: 42, + just_u8: 0, + maybe_u8: None, + default_u8: 42, + + just_i16: 0, + maybe_i16: None, + default_i16: 42, + just_u16: 0, + maybe_u16: None, + default_u16: 42, + + just_i32: 0, + maybe_i32: None, + default_i32: 42, + just_u32: 0, + maybe_u32: None, + default_u32: 42, + + just_i64: 0, + maybe_i64: None, + default_i64: 42, + just_u64: 0, + maybe_u64: None, + default_u64: 42, + + just_f32: 0.0, + maybe_f32: None, + default_f32: 42.0, + just_f64: 0.0, + maybe_f64: None, + default_f64: 42.0, + + just_bool: false, + maybe_bool: None, + default_bool: true, + + just_enum: OptionalByte::None, + maybe_enum: None, + default_enum: OptionalByte::One, + } + ); +} diff --git a/tests/service_test_generated.py b/tests/service_test_generated.py index abc9e920418..1fa9b099a07 100644 --- a/tests/service_test_generated.py +++ b/tests/service_test_generated.py @@ -2,61 +2,57 @@ # namespace: example -from typing import Any import flatbuffers - - +from typing import Any class HelloRequest(object): - __slots__ = ['_tab'] - - @classmethod - def GetRootAs(cls, buf, offset: int = 0): - n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) - x = HelloRequest() - x.Init(buf, n + offset) - return x - - @classmethod - def GetRootAsHelloRequest(cls, buf, offset=0): - """This method is deprecated. Please switch to GetRootAs.""" - return cls.GetRootAs(buf, offset) - - # HelloRequest - def Init(self, buf: bytes, pos: int): - self._tab = flatbuffers.table.Table(buf, pos) - + __slots__ = ['_tab'] + + @classmethod + def GetRootAs(cls, buf, offset: int = 0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = HelloRequest() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsHelloRequest(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) + # HelloRequest + def Init(self, buf: bytes, pos: int): + self._tab = flatbuffers.table.Table(buf, pos) def HelloRequestStart(builder: flatbuffers.Builder): - builder.StartObject(0) - + builder.StartObject(0) def HelloRequestEnd(builder: flatbuffers.Builder) -> int: - return builder.EndObject() - + return builder.EndObject() -class HelloResponse(object): - __slots__ = ['_tab'] - @classmethod - def GetRootAs(cls, buf, offset: int = 0): - n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) - x = HelloResponse() - x.Init(buf, n + offset) - return x - @classmethod - def GetRootAsHelloResponse(cls, buf, offset=0): - """This method is deprecated. Please switch to GetRootAs.""" - return cls.GetRootAs(buf, offset) +class HelloResponse(object): + __slots__ = ['_tab'] + + @classmethod + def GetRootAs(cls, buf, offset: int = 0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = HelloResponse() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsHelloResponse(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) + # HelloResponse + def Init(self, buf: bytes, pos: int): + self._tab = flatbuffers.table.Table(buf, pos) - # HelloResponse - def Init(self, buf: bytes, pos: int): - self._tab = flatbuffers.table.Table(buf, pos) +def HelloResponseStart(builder: flatbuffers.Builder): + builder.StartObject(0) +def HelloResponseEnd(builder: flatbuffers.Builder) -> int: + return builder.EndObject() -def HelloResponseStart(builder: flatbuffers.Builder): - builder.StartObject(0) -def HelloResponseEnd(builder: flatbuffers.Builder) -> int: - return builder.EndObject() diff --git a/tests/service_test_grpc_fb.py b/tests/service_test_grpc_fb.py new file mode 100644 index 00000000000..27284ea9486 --- /dev/null +++ b/tests/service_test_grpc_fb.py @@ -0,0 +1,97 @@ +# Generated by the gRPC FlatBuffers compiler. DO NOT EDIT! + +import flatbuffers +import grpc + +from service_test_generated import HelloRequest, HelloResponse + + +def _serialize_to_bytes(table): + buf = table._tab.Bytes + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, 0) + if table._tab.Pos != n: + raise ValueError('must be a top-level table') + return bytes(buf) + + +class HelloServiceStub(object): + '''Interface exported by the server.''' + + def __init__(self, channel): + '''Constructor. + + Args: + channel: A grpc.Channel. + ''' + + self.Hello = channel.unary_unary( + method='/example.HelloService/Hello', + request_serializer=_serialize_to_bytes, + response_deserializer=HelloResponse.GetRootAs) + + self.StreamClient = channel.stream_unary( + method='/example.HelloService/StreamClient', + request_serializer=_serialize_to_bytes, + response_deserializer=HelloResponse.GetRootAs) + + self.StreamServer = channel.unary_stream( + method='/example.HelloService/StreamServer', + request_serializer=_serialize_to_bytes, + response_deserializer=HelloResponse.GetRootAs) + + self.Stream = channel.stream_stream( + method='/example.HelloService/Stream', + request_serializer=_serialize_to_bytes, + response_deserializer=HelloResponse.GetRootAs) + + +class HelloServiceServicer(object): + '''Interface exported by the server.''' + + def Hello(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StreamClient(self, request_iterator, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StreamServer(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Stream(self, request_iterator, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_HelloServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Hello': grpc.unary_unary_rpc_method_handler( + servicer.Hello, + request_deserializer=HelloRequest.GetRootAs, + response_serializer=_serialize_to_bytes), + 'StreamClient': grpc.stream_unary_rpc_method_handler( + servicer.StreamClient, + request_deserializer=HelloRequest.GetRootAs, + response_serializer=_serialize_to_bytes), + 'StreamServer': grpc.unary_stream_rpc_method_handler( + servicer.StreamServer, + request_deserializer=HelloRequest.GetRootAs, + response_serializer=_serialize_to_bytes), + 'Stream': grpc.stream_stream_rpc_method_handler( + servicer.Stream, + request_deserializer=HelloRequest.GetRootAs, + response_serializer=_serialize_to_bytes), + } + + generic_handler = grpc.method_handlers_generic_handler( + 'example.HelloService', rpc_method_handlers) + + server.add_generic_rpc_handlers((generic_handler,)) + + diff --git a/tests/service_test_grpc_fb.pyi b/tests/service_test_grpc_fb.pyi new file mode 100644 index 00000000000..d0f38aa4f3c --- /dev/null +++ b/tests/service_test_grpc_fb.pyi @@ -0,0 +1,27 @@ +# Generated by the gRPC FlatBuffers compiler. DO NOT EDIT! + +from __future__ import annotations + +import grpc +import typing + +from service_test_generated import HelloRequest, HelloResponse + + +class HelloServiceStub(object): + def __init__(self, channel: grpc.Channel) -> None: ... + def Hello(self, request: HelloRequest) -> HelloResponse: ... + def StreamClient(self, request_iterator: typing.Iterator[HelloRequest]) -> HelloResponse: ... + def StreamServer(self, request: HelloRequest) -> typing.Iterator[HelloResponse]: ... + def Stream(self, request_iterator: typing.Iterator[HelloRequest]) -> typing.Iterator[HelloResponse]: ... + + +class HelloServiceServicer(object): + def Hello(self, request: HelloRequest, context: grpc.ServicerContext) -> HelloResponse: ... + def StreamClient(self, request_iterator: typing.Iterator[HelloRequest], context: grpc.ServicerContext) -> HelloResponse: ... + def StreamServer(self, request: HelloRequest, context: grpc.ServicerContext) -> typing.Iterator[HelloResponse]: ... + def Stream(self, request_iterator: typing.Iterator[HelloRequest], context: grpc.ServicerContext) -> typing.Iterator[HelloResponse]: ... + + +def add_HelloServiceServicer_to_server(servicer: HelloServiceServicer, server: grpc.Server) -> None: ... + diff --git a/tests/ts/JavaScriptComplexArraysPreserveCaseTest.js b/tests/ts/JavaScriptComplexArraysPreserveCaseTest.js new file mode 100644 index 00000000000..b03ce86a6d3 --- /dev/null +++ b/tests/ts/JavaScriptComplexArraysPreserveCaseTest.js @@ -0,0 +1,149 @@ +/* global BigInt */ + +import assert from 'assert'; +import * as flatbuffers from 'flatbuffers'; +import {readFileSync, writeFileSync} from 'fs'; + +import {ArrayStructT} from './preserve_case/arrays_test_complex/my-game/example/ArrayStruct.js' +import {ArrayTable, ArrayTableT} from './preserve_case/arrays_test_complex/my-game/example/ArrayTable.js' +import {InnerStructT} from './preserve_case/arrays_test_complex/my-game/example/InnerStruct.js' +import {NestedStructT} from './preserve_case/arrays_test_complex/my-game/example/NestedStruct.js' +import {OuterStructT} from './preserve_case/arrays_test_complex/my-game/example/OuterStruct.js' +import {TestEnum} from './preserve_case/arrays_test_complex/my-game/example/TestEnum.js' +import {applyPrototypeAliases} from './preserve_case_aliases.js' + +applyPrototypeAliases([ + ArrayTable, + ArrayTableT, + ArrayStructT, + InnerStructT, + NestedStructT, + OuterStructT, +]); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +BigInt.prototype.toJSON = function() { + return this.toString(); +}; +function fbObjToObj(fbObj) { + const ret = {}; + for (const propName of Object.keys(fbObj)) { + const key = propName; + const prop = fbObj[key]; + if (prop.valueOf) { + ret[key] = prop.valueOf(); + } else if (typeof prop === 'object') { + ret[key] = fbObjToObj(prop); + } + } + return ret; +} +function testBuild(monFile, jsFile) { + const arrayTable = new ArrayTableT( + 'Complex Array Test', + new ArrayStructT( + 221.139008, + [-700, -600, -500, -400, -300, -200, -100, 0, 100, 200, 300, 400, 500, 600, 700], + 13, + [ + new NestedStructT( + [233, -123], + TestEnum.B, + [TestEnum.A, TestEnum.C], + [ + new OuterStructT( + false, + 123.456, + new InnerStructT( + 123456792.0, + [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + 91, + BigInt('9007199254740999') + ), + [ + new InnerStructT( + -987654321.9876, + [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243], + 123, + BigInt('9007199254741000') + ), + new InnerStructT( + 123000987.9876, + [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113], + -123, + BigInt('9007199254741000') + ), + ], + new InnerStructT( + 987654321.9876, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + 19, + BigInt('9007199254741000') + ), + [111000111.222, 222000222.111, 333000333.333, 444000444.444] + ), + ] + ), + ], + -123456789 + ) + ); + const builder = new flatbuffers.Builder(); + builder.finish(arrayTable.pack(builder)); + if (jsFile) { + const obj = fbObjToObj(arrayTable); + writeFileSync(jsFile, `export default ${JSON.stringify(obj, null, 2)}`); + } + if (monFile) { + writeFileSync(monFile, builder.asUint8Array()); + } + return builder.asUint8Array(); +} +function testParse(monFile, jsFile, buffer) { + if (!buffer) { + if (!monFile) { + console.log(`Please specify mon file read the buffer from.`); + process.exit(1); + } + buffer = readFileSync(monFile); + } + const byteBuffer = new flatbuffers.ByteBuffer(new Uint8Array(buffer)); + const arrayTable = ArrayTable.getRootAsArrayTable(byteBuffer).unpack(); + const json = JSON.stringify(arrayTable, null, 2); + if (jsFile) { + writeFileSync(jsFile, `export default ${json}`); + } + return arrayTable; +} +if (process.argv[2] === 'build') { + testBuild(process.argv[3], process.argv[4]); +} else if (process.argv[2] === 'parse') { + testParse(process.argv[3], process.argv[4], null); +} else { + const arr = testBuild(null, null); + const parsed = testParse(null, null, Buffer.from(arr)); + assert.strictEqual(parsed.a, 'Complex Array Test', 'String Test'); + assert.strictEqual( + parsed?.c_underscore?.a_underscore, 221.13900756835938, 'Float Test'); + assert.deepEqual( + parsed?.c_underscore?.b_underscore, + [ + -700, -600, -500, -400, -300, -200, -100, 0, 100, 200, 300, 400, 500, + 600, 700 + ], + 'Array of signed integers'); + assert.strictEqual( + parsed?.c_underscore.d?.[0].d_outer[0].d[1].a, 123000987.9876, + 'Float in deep'); + assert.deepEqual( + parsed?.c_underscore?.d[0].d_outer?.[0]?.e, { + a: 987654321.9876, + b: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + c: 19, + d_underscore: '9007199254741000', + }, + 'Object in deep'); + assert.deepEqual(parsed?.c_underscore.g, ['0', '0'], 'Last object'); + + console.log('Arrays test: completed successfully'); +} diff --git a/tests/ts/JavaScriptPreserveCaseTest.js b/tests/ts/JavaScriptPreserveCaseTest.js new file mode 100644 index 00000000000..579a0cdd461 --- /dev/null +++ b/tests/ts/JavaScriptPreserveCaseTest.js @@ -0,0 +1,548 @@ +// Run this using JavaScriptTest.sh +import assert from 'assert' +import * as flatbuffers from 'flatbuffers' +import fs from 'fs' + +import {Any} from './preserve_case/my-game/example/Any.js'; +import {Color} from './preserve_case/my-game/example/Color.js'; +import {Monster, MonsterT} from './preserve_case/my-game/example/Monster.js' +import {Stat} from './preserve_case/my-game/example/Stat.js' +import {Test, TestT} from './preserve_case/my-game/example/Test.js' +import {Vec3} from './preserve_case/my-game/example/Vec3.js' +import {applyPrototypeAliases} from './preserve_case_aliases.js' + +applyPrototypeAliases([Monster, MonsterT, Test, TestT, Vec3, Stat]); + +function main() { + // First, let's test reading a FlatBuffer generated by C++ code: + // This file was generated from monsterdata_test.json + var data = new Uint8Array(fs.readFileSync('../monsterdata_test.mon')); + + // Now test it: + + var bb = new flatbuffers.ByteBuffer(data); + testBuffer(bb); + + // Second, let's create a FlatBuffer from scratch in JavaScript, and test it + // also. We use an initial size of 1 to exercise the reallocation algorithm, + // normally a size larger than the typical FlatBuffer you generate would be + // better for performance. + var fbb = new flatbuffers.Builder(1); + createMonster(fbb); + serializeAndTest(fbb); + testObjApiPack(fbb); + + // clear the builder, repeat tests + var clearIterations = 100; + var startingCapacity = fbb.bb.capacity(); + for (var i = 0; i < clearIterations; i++) { + fbb.clear(); + createMonster(fbb); + serializeAndTest(fbb); + testObjApiPack(fbb); + } + // the capacity of our buffer shouldn't increase with the same size payload + assert.strictEqual(fbb.bb.capacity(), startingCapacity); + + test64bit(); + testUnicode(); + fuzzTest1(); + testNullStrings(); + testSharedStrings(); + testVectorOfStructs(); + testCreateByteVector(); + + console.log('FlatBuffers test: completed successfully'); +} + +function createMonster(fbb) { + // We set up the same values as monsterdata.json: + + var str = fbb.createString('MyMonster'); + + var inv = Monster.createInventoryVector(fbb, [0, 1, 2, 3, 4]); + + var fred = fbb.createString('Fred'); + Monster.startMonster(fbb); + Monster.addName(fbb, fred); + var mon2 = Monster.endMonster(fbb); + + Monster.startTest4Vector(fbb, 2); + Test.createTest(fbb, 10, 20); + Test.createTest(fbb, 30, 40); + var test4 = fbb.endVector(); + + var testArrayOfString = Monster.createTestarrayofstringVector( + fbb, [fbb.createString('test1'), fbb.createString('test2')]); + + var testVectorOfLongs = + Monster.createVectorOfLongsVector(fbb, [1n, 101010100n]); + + Monster.startMonster(fbb); + Monster.addPos(fbb, Vec3.createVec3(fbb, 1, 2, 3, 3, Color.Green, 5, 6)); + Monster.addHp(fbb, 80); + Monster.addName(fbb, str); + Monster.addInventory(fbb, inv); + Monster.addTestType(fbb, Any.Monster); + Monster.addTest(fbb, mon2); + Monster.addTest4(fbb, test4); + Monster.addTestarrayofstring(fbb, testArrayOfString); + Monster.addVectorOfLongs(fbb, testVectorOfLongs); + Monster.addTestbool(fbb, true); + var mon = Monster.endMonster(fbb); + + Monster.finishMonsterBuffer(fbb, mon); +} + +function serializeAndTest(fbb) { + // Write the result to a file for debugging purposes: + // Note that the binaries are not necessarily identical, since the JSON + // parser may serialize in a slightly different order than the above + // JavaScript code. They are functionally equivalent though. + + fs.writeFileSync( + 'monsterdata_javascript_wire.mon', Buffer.from(fbb.asUint8Array())); + + // Tests mutation first. This will verify that we did not trample any other + // part of the byte buffer. + testMutation(fbb.dataBuffer()); + + testBuffer(fbb.dataBuffer()); +} + +function testMutation(bb) { + var monster = Monster.getRootAsMonster(bb); + + monster.mutate_hp(120); + assert.strictEqual(monster.hp(), 120); + + monster.mutate_hp(80); + assert.strictEqual(monster.hp(), 80); + + var manaRes = monster.mutate_mana(10); + assert.strictEqual( + manaRes, false); // Field was NOT present, because default value. + + // TODO: There is not the availability to mutate structs or vectors. +} + +function testObjApiPack(fbb) { + fbb.clear(); + createMonster(fbb); + let monster_t = Monster.getRootAsMonster(fbb.dataBuffer()).unpack(); + fbb.clear(); + Monster.finishMonsterBuffer(fbb, monster_t.pack(fbb)); + serializeAndTest(fbb); +} + +function testObjApiUnpack(monster) { + assert.strictEqual(monster.hp, 80); + assert.strictEqual(monster.mana, 150); // default + + assert.strictEqual(monster.name, 'MyMonster'); + + let pos = monster.pos; + assert.strictEqual(pos.x, 1); + assert.strictEqual(pos.y, 2); + assert.strictEqual(pos.z, 3); + assert.strictEqual(pos.test1, 3); + assert.strictEqual(pos.test2, Color.Green); + let test3 = pos.test3; + assert.strictEqual(test3.a, 5); + assert.strictEqual(test3.b, 6); + + assert.strictEqual(monster.test_type, Any.Monster); + let monster2 = monster.test; + assert.strictEqual(monster2 != null, true); + assert.strictEqual(monster2 instanceof MonsterT, true); + assert.strictEqual(monster2.name, 'Fred'); + + assert.strictEqual(monster.inventory.length, 5); + let invsum = 0; + for (let i = 0; i < monster.inventory.length; i++) { + invsum += monster.inventory[i]; + } + assert.strictEqual(invsum, 10); + + let test_0 = monster.test4[0]; + let test_1 = monster.test4[1]; + assert.strictEqual(monster.test4.length, 2); + assert.strictEqual(test_0.a + test_0.b + test_1.a + test_1.b, 100); + + assert.strictEqual(monster.testarrayofstring.length, 2); + assert.strictEqual(monster.testarrayofstring[0], 'test1'); + assert.strictEqual(monster.testarrayofstring[1], 'test2'); + + assert.strictEqual(monster.testbool, true); +} + +function testBuffer(bb) { + assert.ok(Monster.bufferHasIdentifier(bb)); + + var monster = Monster.getRootAsMonster(bb); + + assert.strictEqual(monster.hp(), 80); + assert.strictEqual(monster.mana(), 150); // default + + assert.strictEqual(monster.name(), 'MyMonster'); + + var pos = monster.pos(); + assert.strictEqual(pos.x(), 1); + assert.strictEqual(pos.y(), 2); + assert.strictEqual(pos.z(), 3); + assert.strictEqual(pos.test1(), 3); + assert.strictEqual(pos.test2(), Color.Green); + var t = pos.test3(); + assert.strictEqual(t.a(), 5); + assert.strictEqual(t.b(), 6); + + assert.strictEqual(monster.test_type(), Any.Monster); + var monster2 = new Monster(); + assert.strictEqual(monster.test(monster2) != null, true); + assert.strictEqual(monster2.name(), 'Fred'); + + assert.strictEqual(monster.inventoryLength(), 5); + var invsum = 0; + for (var i = 0; i < monster.inventoryLength(); i++) { + invsum += monster.inventory(i); + } + assert.strictEqual(invsum, 10); + + var invsum2 = 0; + var invArr = monster.inventoryArray(); + for (var i = 0; i < invArr.length; i++) { + invsum2 += invArr[i]; + } + assert.strictEqual(invsum2, 10); + + let longSum = 0n; + for (let idx = 0; idx < monster.vectorOfLongsLength(); ++idx) { + longSum += monster.vector_of_longs(idx); + } + assert.strictEqual(longSum, 101010101n); + + var test_0 = monster.test4(0); + var test_1 = monster.test4(1); + assert.strictEqual(monster.test4Length(), 2); + assert.strictEqual(test_0.a() + test_0.b() + test_1.a() + test_1.b(), 100); + + assert.strictEqual(monster.testarrayofstringLength(), 2); + assert.strictEqual(monster.testarrayofstring(0), 'test1'); + assert.strictEqual(monster.testarrayofstring(1), 'test2'); + + assert.strictEqual(monster.testbool(), true); + + let monster_t = monster.unpack(); + testObjApiUnpack(monster_t); + + let monster2_t = new MonsterT(); + monster.unpackTo(monster2_t); + testObjApiUnpack(monster2_t); +} + +function test64bit() { + var fbb = new flatbuffers.Builder(); + var required = fbb.createString('required'); + + Stat.startStat(fbb); + var stat2 = Stat.endStat(fbb); + + Monster.startMonster(fbb); + Monster.addName(fbb, required); + Monster.addTestempty(fbb, stat2); + var mon2 = Monster.endMonster(fbb); + + Stat.startStat(fbb); + // 2541551405100253985 = 0x2345678987654321 + Stat.addVal(fbb, 0x2345678987654321n); + var stat = Stat.endStat(fbb); + + Monster.startMonster(fbb); + Monster.addName(fbb, required); + Monster.addEnemy(fbb, mon2); + Monster.addTestempty(fbb, stat); + var mon = Monster.endMonster(fbb); + + Monster.finishMonsterBuffer(fbb, mon); + var bytes = fbb.asUint8Array(); + + //////////////////////////////////////////////////////////////// + + var bb = new flatbuffers.ByteBuffer(bytes); + assert.ok(Monster.bufferHasIdentifier(bb)); + var mon = Monster.getRootAsMonster(bb); + + var stat = mon.testempty(); + assert.strictEqual(stat != null, true); + assert.strictEqual(stat.val() != null, true); + assert.strictEqual(stat.val(), 2541551405100253985n); + + var mon2 = mon.enemy(); + assert.strictEqual(mon2 != null, true); + stat = mon2.testempty(); + assert.strictEqual(stat != null, true); + assert.strictEqual(stat.val() != null, true); + assert.strictEqual(stat.val(), 0n); // default value +} + +function testUnicode() { + var correct = fs.readFileSync('../unicode_test.mon'); + var json = JSON.parse(fs.readFileSync('../unicode_test.json', 'utf8')); + + // Test reading + function testReadingUnicode(bb) { + var monster = Monster.getRootAsMonster(bb); + assert.strictEqual(monster.name(), json.name); + assert.deepEqual( + Buffer.from(monster.name(flatbuffers.Encoding.UTF8_BYTES)), + Buffer.from(json.name)); + assert.strictEqual( + monster.testarrayoftablesLength(), json.testarrayoftables.length); + json.testarrayoftables.forEach(function(table, i) { + var value = monster.testarrayoftables(i); + assert.strictEqual(value.name(), table.name); + assert.deepEqual( + Buffer.from(value.name(flatbuffers.Encoding.UTF8_BYTES)), + Buffer.from(table.name)); + }); + assert.strictEqual( + monster.testarrayofstringLength(), json.testarrayofstring.length); + json.testarrayofstring.forEach(function(string, i) { + assert.strictEqual(monster.testarrayofstring(i), string); + assert.deepEqual( + Buffer.from( + monster.testarrayofstring(i, flatbuffers.Encoding.UTF8_BYTES)), + Buffer.from(string)); + }); + } + testReadingUnicode(new flatbuffers.ByteBuffer(new Uint8Array(correct))); + + // Test writing + var fbb = new flatbuffers.Builder(); + var name = fbb.createString(json.name); + var testarrayoftablesOffsets = json.testarrayoftables.map(function(table) { + var name = fbb.createString(new Uint8Array(Buffer.from(table.name))); + Monster.startMonster(fbb); + Monster.addName(fbb, name); + return Monster.endMonster(fbb); + }); + var testarrayoftablesOffset = + Monster.createTestarrayoftablesVector(fbb, testarrayoftablesOffsets); + var testarrayofstringOffset = Monster.createTestarrayofstringVector( + fbb, json.testarrayofstring.map(function(string) { + return fbb.createString(string); + })); + Monster.startMonster(fbb); + Monster.addTestarrayofstring(fbb, testarrayofstringOffset); + Monster.addTestarrayoftables(fbb, testarrayoftablesOffset); + Monster.addName(fbb, name); + Monster.finishSizePrefixedMonsterBuffer(fbb, Monster.endMonster(fbb)); + var bb = new flatbuffers.ByteBuffer(fbb.asUint8Array()) + bb.setPosition(4); + testReadingUnicode(bb); +} + +var __imul = Math.imul ? Math.imul : function(a, b) { + var ah = a >> 16 & 65535; + var bh = b >> 16 & 65535; + var al = a & 65535; + var bl = b & 65535; + return al * bl + (ah * bl + al * bh << 16) | 0; +}; + +// Include simple random number generator to ensure results will be the +// same cross platform. +// http://en.wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator +var lcg_seed = 48271; + +function lcg_rand() { + return lcg_seed = (__imul(lcg_seed, 279470273) >>> 0) % 4294967291; +} + +function lcg_reset() { + lcg_seed = 48271; +} + +// Converts a Field ID to a virtual table offset. +function fieldIndexToOffset(field_id) { + // Should correspond to what EndTable() below builds up. + var fixed_fields = 2; // Vtable size and Object Size. + return (field_id + fixed_fields) * 2; +} + +// Low level stress/fuzz test: serialize/deserialize a variety of +// different kinds of data in different combinations +function fuzzTest1() { + // Values we're testing against: chosen to ensure no bits get chopped + // off anywhere, and also be different from eachother. + var bool_val = true; + var char_val = -127; // 0x81 + var uchar_val = 0xFF; + var short_val = -32222; // 0x8222; + var ushort_val = 0xFEEE; + var int_val = 0x83333333 | 0; + var uint_val = 0xFDDDDDDD; + var long_val = BigInt.asIntN(64, 0x8444444444444444n); + var ulong_val = BigInt.asUintN(64, 0xFCCCCCCCCCCCCCCCn); + var float_val = new Float32Array([3.14159])[0]; + var double_val = 3.14159265359; + + var test_values_max = 11; + var fields_per_object = 4; + var num_fuzz_objects = 10000; // The higher, the more thorough :) + + var builder = new flatbuffers.Builder(); + + lcg_reset(); // Keep it deterministic. + + var objects = []; + + // Generate num_fuzz_objects random objects each consisting of + // fields_per_object fields, each of a random type. + for (var i = 0; i < num_fuzz_objects; i++) { + builder.startObject(fields_per_object); + for (var f = 0; f < fields_per_object; f++) { + var choice = lcg_rand() % test_values_max; + switch (choice) { + case 0: + builder.addFieldInt8(f, bool_val, 0); + break; + case 1: + builder.addFieldInt8(f, char_val, 0); + break; + case 2: + builder.addFieldInt8(f, uchar_val, 0); + break; + case 3: + builder.addFieldInt16(f, short_val, 0); + break; + case 4: + builder.addFieldInt16(f, ushort_val, 0); + break; + case 5: + builder.addFieldInt32(f, int_val, 0); + break; + case 6: + builder.addFieldInt32(f, uint_val, 0); + break; + case 7: + builder.addFieldInt64(f, long_val, 0n); + break; + case 8: + builder.addFieldInt64(f, ulong_val, 0n); + break; + case 9: + builder.addFieldFloat32(f, float_val, 0); + break; + case 10: + builder.addFieldFloat64(f, double_val, 0); + break; + } + } + objects.push(builder.endObject()); + } + builder.prep(8, 0); // Align whole buffer. + + lcg_reset(); // Reset. + + builder.finish(objects[objects.length - 1]); + var bytes = new Uint8Array(builder.asUint8Array()); + var view = new DataView(bytes.buffer); + + // Test that all objects we generated are readable and return the + // expected values. We generate random objects in the same order + // so this is deterministic. + for (var i = 0; i < num_fuzz_objects; i++) { + var offset = bytes.length - objects[i]; + for (var f = 0; f < fields_per_object; f++) { + var choice = lcg_rand() % test_values_max; + var vtable_offset = fieldIndexToOffset(f); + var vtable = offset - view.getInt32(offset, true); + assert.ok(vtable_offset < view.getInt16(vtable, true)); + var field_offset = offset + view.getInt16(vtable + vtable_offset, true); + switch (choice) { + case 0: + assert.strictEqual(!!view.getInt8(field_offset), bool_val); + break; + case 1: + assert.strictEqual(view.getInt8(field_offset), char_val); + break; + case 2: + assert.strictEqual(view.getUint8(field_offset), uchar_val); + break; + case 3: + assert.strictEqual(view.getInt16(field_offset, true), short_val); + break; + case 4: + assert.strictEqual(view.getUint16(field_offset, true), ushort_val); + break; + case 5: + assert.strictEqual(view.getInt32(field_offset, true), int_val); + break; + case 6: + assert.strictEqual(view.getUint32(field_offset, true), uint_val); + break; + case 7: + assert.strictEqual(view.getBigInt64(field_offset, true), long_val); + break; + case 8: + assert.strictEqual(view.getBigUint64(field_offset, true), ulong_val); + break; + case 9: + assert.strictEqual(view.getFloat32(field_offset, true), float_val); + break; + case 10: + assert.strictEqual(view.getFloat64(field_offset, true), double_val); + break; + } + } + } +} + +function testSharedStrings() { + var shared_string = 'Hello world'; + var builder = new flatbuffers.Builder(); + let mainOffset = builder.createSharedString(shared_string); + assert.strictEqual(builder.createSharedString(shared_string), mainOffset); +} + +function testNullStrings() { + var builder = new flatbuffers.Builder(); + assert.strictEqual(builder.createString(null), 0); + assert.strictEqual(builder.createSharedString(null), 0); + assert.strictEqual(builder.createString(undefined), 0); + assert.strictEqual(builder.createSharedString(undefined), 0); +} + +function testVectorOfStructs() { + let monster = new MonsterT(); + monster.name = 'testVectorOfStructs'; + monster.test4 = [new TestT(1, 2), new TestT(3, 4)]; + + let builder = new flatbuffers.Builder(); + builder.finish(monster.pack(builder)); + + let decodedMonster = Monster.getRootAsMonster(builder.dataBuffer()).unpack(); + assert.strictEqual(decodedMonster.test4[0].a, 1); + assert.strictEqual(decodedMonster.test4[0].b, 2); + assert.strictEqual(decodedMonster.test4[1].a, 3); + assert.strictEqual(decodedMonster.test4[1].b, 4); +} + +function testCreateByteVector() { + const data = Uint8Array.from([1, 2, 3, 4, 5]); + + const builder = new flatbuffers.Builder(); + const required = builder.createString('required'); + const offset = builder.createByteVector(data); + + Monster.startMonster(builder); + Monster.addName(builder, required); + Monster.addInventory(builder, offset) + builder.finish(Monster.endMonster(builder)); + + let decodedMonster = Monster.getRootAsMonster(builder.dataBuffer()); + assert.deepEqual(decodedMonster.inventoryArray(), data); +} + +main(); diff --git a/tests/ts/JavaScriptTestv1PreserveCase.cjs b/tests/ts/JavaScriptTestv1PreserveCase.cjs new file mode 100644 index 00000000000..b9e826f40dd --- /dev/null +++ b/tests/ts/JavaScriptTestv1PreserveCase.cjs @@ -0,0 +1,409 @@ +// Run this using JavaScriptTest.sh +var assert = require('assert'); +var fs = require('fs'); + +var flatbuffers = require('../../js/flatbuffers'); +var MyGame = require(process.argv[2]).MyGame; + +function applyPrototypeAliases(classes) { + var visited = new Set(); + classes.forEach(function(ctor) { + if (typeof ctor !== 'function' || ctor === null || visited.has(ctor)) { + return; + } + visited.add(ctor); + var proto = ctor.prototype; + if (!proto) { + return; + } + Object.getOwnPropertyNames(proto).forEach(function(key) { + if (key === 'constructor') { + return; + } + var value = proto[key]; + if (typeof value !== 'function' || key.indexOf('_') === -1) { + return; + } + var camel = key.replace(/_+([a-zA-Z0-9])/g, function(_, ch) { + return ch.toUpperCase(); + }); + if (camel !== key && !Object.prototype.hasOwnProperty.call(proto, camel)) { + Object.defineProperty(proto, camel, { + value: value, + writable: true, + configurable: true, + }); + } + }); + }); +} + +applyPrototypeAliases([ + MyGame && MyGame.Example && MyGame.Example.Monster, + MyGame && MyGame.Example && MyGame.Example.MonsterT, + MyGame && MyGame.Example && MyGame.Example.Test, + MyGame && MyGame.Example && MyGame.Example.TestT, + MyGame && MyGame.Example && MyGame.Example.Vec3, + MyGame && MyGame.Example && MyGame.Example.Stat, +]); + +function main() { + + // First, let's test reading a FlatBuffer generated by C++ code: + // This file was generated from monsterdata_test.json + var data = new Uint8Array(fs.readFileSync('../monsterdata_test.mon')); + + // Now test it: + + var bb = new flatbuffers.ByteBuffer(data); + testBuffer(bb); + + // Second, let's create a FlatBuffer from scratch in JavaScript, and test it also. + // We use an initial size of 1 to exercise the reallocation algorithm, + // normally a size larger than the typical FlatBuffer you generate would be + // better for performance. + var fbb = new flatbuffers.Builder(1); + createMonster(fbb); + serializeAndTest(fbb); + + // clear the builder, repeat tests + var clearIterations = 100; + var startingCapacity = fbb.bb.capacity(); + for (var i = 0; i < clearIterations; i++) { + fbb.clear(); + createMonster(fbb); + serializeAndTest(fbb); + } + // the capacity of our buffer shouldn't increase with the same size payload + assert.strictEqual(fbb.bb.capacity(), startingCapacity); + + test64bit(); + testUnicode(); + fuzzTest1(); + + console.log('FlatBuffers test: completed successfully'); +} + +function createMonster(fbb) { + // We set up the same values as monsterdata.json: + + var str = fbb.createString('MyMonster'); + + var inv = MyGame.Example.Monster.createInventoryVector(fbb, [0, 1, 2, 3, 4]); + + var fred = fbb.createString('Fred'); + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addName(fbb, fred); + var mon2 = MyGame.Example.Monster.endMonster(fbb); + + MyGame.Example.Monster.startTest4Vector(fbb, 2); + MyGame.Example.Test.createTest(fbb, 10, 20); + MyGame.Example.Test.createTest(fbb, 30, 40); + var test4 = fbb.endVector(); + + var testArrayOfString = MyGame.Example.Monster.createTestarrayofstringVector(fbb, [ + fbb.createString('test1'), + fbb.createString('test2') + ]); + + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addPos(fbb, MyGame.Example.Vec3.createVec3(fbb, 1, 2, 3, 3, MyGame.Example.Color.Green, 5, 6)); + MyGame.Example.Monster.addHp(fbb, 80); + MyGame.Example.Monster.addName(fbb, str); + MyGame.Example.Monster.addInventory(fbb, inv); + MyGame.Example.Monster.addTestType(fbb, MyGame.Example.Any.Monster); + MyGame.Example.Monster.addTest(fbb, mon2); + MyGame.Example.Monster.addTest4(fbb, test4); + MyGame.Example.Monster.addTestarrayofstring(fbb, testArrayOfString); + MyGame.Example.Monster.addTestbool(fbb, true); + var mon = MyGame.Example.Monster.endMonster(fbb); + + MyGame.Example.Monster.finishMonsterBuffer(fbb, mon); +} + +function serializeAndTest(fbb) { + // Write the result to a file for debugging purposes: + // Note that the binaries are not necessarily identical, since the JSON + // parser may serialize in a slightly different order than the above + // JavaScript code. They are functionally equivalent though. + + fs.writeFileSync('monsterdata_javascript_wire.mon', new Buffer(fbb.asUint8Array())); + + // Tests mutation first. This will verify that we did not trample any other + // part of the byte buffer. + testMutation(fbb.dataBuffer()); + + testBuffer(fbb.dataBuffer()); +} + +function testMutation(bb) { + var monster = MyGame.Example.Monster.getRootAsMonster(bb); + + monster.mutate_hp(120); + assert.strictEqual(monster.hp(), 120); + + monster.mutate_hp(80); + assert.strictEqual(monster.hp(), 80); + + var manaRes = monster.mutate_mana(10); + assert.strictEqual(manaRes, false); // Field was NOT present, because default value. + + // TODO: There is not the availability to mutate structs or vectors. +} + +function testBuffer(bb) { + assert.ok(MyGame.Example.Monster.bufferHasIdentifier(bb)); + + var monster = MyGame.Example.Monster.getRootAsMonster(bb); + + assert.strictEqual(monster.hp(), 80); + assert.strictEqual(monster.mana(), 150); // default + + assert.strictEqual(monster.name(), 'MyMonster'); + + var pos = monster.pos(); + assert.strictEqual(pos.x(), 1); + assert.strictEqual(pos.y(), 2); + assert.strictEqual(pos.z(), 3); + assert.strictEqual(pos.test1(), 3); + assert.strictEqual(pos.test2(), MyGame.Example.Color.Green); + var t = pos.test3(); + assert.strictEqual(t.a(), 5); + assert.strictEqual(t.b(), 6); + + assert.strictEqual(monster.test_type(), MyGame.Example.Any.Monster); + var monster2 = new MyGame.Example.Monster(); + assert.strictEqual(monster.test(monster2) != null, true); + assert.strictEqual(monster2.name(), 'Fred'); + + assert.strictEqual(monster.inventoryLength(), 5); + var invsum = 0; + for (var i = 0; i < monster.inventoryLength(); i++) { + invsum += monster.inventory(i); + } + assert.strictEqual(invsum, 10); + + var invsum2 = 0; + var invArr = monster.inventoryArray(); + for (var i = 0; i < invArr.length; i++) { + invsum2 += invArr[i]; + } + assert.strictEqual(invsum2, 10); + + var test_0 = monster.test4(0); + var test_1 = monster.test4(1); + assert.strictEqual(monster.test4Length(), 2); + assert.strictEqual(test_0.a() + test_0.b() + test_1.a() + test_1.b(), 100); + + assert.strictEqual(monster.testarrayofstringLength(), 2); + assert.strictEqual(monster.testarrayofstring(0), 'test1'); + assert.strictEqual(monster.testarrayofstring(1), 'test2'); + + assert.strictEqual(monster.testbool(), true); +} + +function test64bit() { + var fbb = new flatbuffers.Builder(); + var required = fbb.createString('required'); + + MyGame.Example.Stat.startStat(fbb); + var stat2 = MyGame.Example.Stat.endStat(fbb); + + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addName(fbb, required); + MyGame.Example.Monster.addTestempty(fbb, stat2); + var mon2 = MyGame.Example.Monster.endMonster(fbb); + + MyGame.Example.Stat.startStat(fbb); + MyGame.Example.Stat.addVal(fbb, 0x2345678987654321n); + var stat = MyGame.Example.Stat.endStat(fbb); + + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addName(fbb, required); + MyGame.Example.Monster.addEnemy(fbb, mon2); + MyGame.Example.Monster.addTestempty(fbb, stat); + var mon = MyGame.Example.Monster.endMonster(fbb); + + MyGame.Example.Monster.finishMonsterBuffer(fbb, mon); + var bytes = fbb.asUint8Array(); + + //////////////////////////////////////////////////////////////// + + var bb = new flatbuffers.ByteBuffer(bytes); + assert.ok(MyGame.Example.Monster.bufferHasIdentifier(bb)); + var mon = MyGame.Example.Monster.getRootAsMonster(bb); + + var stat = mon.testempty(); + assert.strictEqual(stat != null, true); + assert.strictEqual(stat.val() != null, true); + assert.strictEqual(stat.val(), 2541551405100253985n); + + var mon2 = mon.enemy(); + assert.strictEqual(mon2 != null, true); + stat = mon2.testempty(); + assert.strictEqual(stat != null, true); + assert.strictEqual(stat.val() != null, true); + assert.strictEqual(stat.val(), 0n); // default value +} + +function testUnicode() { + var correct = fs.readFileSync('unicode_test.mon'); + var json = JSON.parse(fs.readFileSync('../unicode_test.json', 'utf8')); + + // Test reading + function testReadingUnicode(bb) { + var monster = MyGame.Example.Monster.getRootAsMonster(bb); + assert.strictEqual(monster.name(), json.name); + assert.deepEqual(new Buffer(monster.name(flatbuffers.Encoding.UTF8_BYTES)), new Buffer(json.name)); + assert.strictEqual(monster.testarrayoftablesLength(), json.testarrayoftables.length); + json.testarrayoftables.forEach(function(table, i) { + var value = monster.testarrayoftables(i); + assert.strictEqual(value.name(), table.name); + assert.deepEqual(new Buffer(value.name(flatbuffers.Encoding.UTF8_BYTES)), new Buffer(table.name)); + }); + assert.strictEqual(monster.testarrayofstringLength(), json.testarrayofstring.length); + json.testarrayofstring.forEach(function(string, i) { + assert.strictEqual(monster.testarrayofstring(i), string); + assert.deepEqual(new Buffer(monster.testarrayofstring(i, flatbuffers.Encoding.UTF8_BYTES)), new Buffer(string)); + }); + } + testReadingUnicode(new flatbuffers.ByteBuffer(new Uint8Array(correct))); + + // Test writing + var fbb = new flatbuffers.Builder(); + var name = fbb.createString(json.name); + var testarrayoftablesOffsets = json.testarrayoftables.map(function(table) { + var name = fbb.createString(new Uint8Array(new Buffer(table.name))); + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addName(fbb, name); + return MyGame.Example.Monster.endMonster(fbb); + }); + var testarrayoftablesOffset = MyGame.Example.Monster.createTestarrayoftablesVector(fbb, + testarrayoftablesOffsets); + var testarrayofstringOffset = MyGame.Example.Monster.createTestarrayofstringVector(fbb, + json.testarrayofstring.map(function(string) { return fbb.createString(string); })); + MyGame.Example.Monster.startMonster(fbb); + MyGame.Example.Monster.addTestarrayofstring(fbb, testarrayofstringOffset); + MyGame.Example.Monster.addTestarrayoftables(fbb, testarrayoftablesOffset); + MyGame.Example.Monster.addName(fbb, name); + MyGame.Example.Monster.finishSizePrefixedMonsterBuffer(fbb, MyGame.Example.Monster.endMonster(fbb)); + var bb = new flatbuffers.ByteBuffer(fbb.asUint8Array()) + bb.setPosition(4); + testReadingUnicode(bb); +} + +var __imul = Math.imul ? Math.imul : function(a, b) { + var ah = a >> 16 & 65535; + var bh = b >> 16 & 65535; + var al = a & 65535; + var bl = b & 65535; + return al * bl + (ah * bl + al * bh << 16) | 0; +}; + +// Include simple random number generator to ensure results will be the +// same cross platform. +// http://en.wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator +var lcg_seed = 48271; + +function lcg_rand() { + return lcg_seed = (__imul(lcg_seed, 279470273) >>> 0) % 4294967291; +} + +function lcg_reset() { + lcg_seed = 48271; +} + +// Converts a Field ID to a virtual table offset. +function fieldIndexToOffset(field_id) { + // Should correspond to what EndTable() below builds up. + var fixed_fields = 2; // Vtable size and Object Size. + return (field_id + fixed_fields) * 2; +} + +// Low level stress/fuzz test: serialize/deserialize a variety of +// different kinds of data in different combinations +function fuzzTest1() { + + // Values we're testing against: chosen to ensure no bits get chopped + // off anywhere, and also be different from eachother. + var bool_val = true; + var char_val = -127; // 0x81 + var uchar_val = 0xFF; + var short_val = -32222; // 0x8222; + var ushort_val = 0xFEEE; + var int_val = 0x83333333 | 0; + var uint_val = 0xFDDDDDDD; + var long_val = BigInt.asIntN(64, 0x8444444444444444n); + var ulong_val = BigInt.asUintN(64, 0xFCCCCCCCCCCCCCCCn); + var float_val = new Float32Array([3.14159])[0]; + var double_val = 3.14159265359; + + var test_values_max = 11; + var fields_per_object = 4; + var num_fuzz_objects = 10000; // The higher, the more thorough :) + + var builder = new flatbuffers.Builder(); + + lcg_reset(); // Keep it deterministic. + + var objects = []; + + // Generate num_fuzz_objects random objects each consisting of + // fields_per_object fields, each of a random type. + for (var i = 0; i < num_fuzz_objects; i++) { + builder.startObject(fields_per_object); + for (var f = 0; f < fields_per_object; f++) { + var choice = lcg_rand() % test_values_max; + switch (choice) { + case 0: builder.addFieldInt8(f, bool_val, 0); break; + case 1: builder.addFieldInt8(f, char_val, 0); break; + case 2: builder.addFieldInt8(f, uchar_val, 0); break; + case 3: builder.addFieldInt16(f, short_val, 0); break; + case 4: builder.addFieldInt16(f, ushort_val, 0); break; + case 5: builder.addFieldInt32(f, int_val, 0); break; + case 6: builder.addFieldInt32(f, uint_val, 0); break; + case 7: builder.addFieldInt64(f, long_val, 0n); break; + case 8: builder.addFieldInt64(f, ulong_val, 0n); break; + case 9: builder.addFieldFloat32(f, float_val, 0); break; + case 10: builder.addFieldFloat64(f, double_val, 0); break; + } + } + objects.push(builder.endObject()); + } + builder.prep(8, 0); // Align whole buffer. + + lcg_reset(); // Reset. + + builder.finish(objects[objects.length - 1]); + var bytes = new Uint8Array(builder.asUint8Array()); + var view = new DataView(bytes.buffer); + + // Test that all objects we generated are readable and return the + // expected values. We generate random objects in the same order + // so this is deterministic. + for (var i = 0; i < num_fuzz_objects; i++) { + var offset = bytes.length - objects[i]; + for (var f = 0; f < fields_per_object; f++) { + var choice = lcg_rand() % test_values_max; + var vtable_offset = fieldIndexToOffset(f); + var vtable = offset - view.getInt32(offset, true); + assert.ok(vtable_offset < view.getInt16(vtable, true)); + var field_offset = offset + view.getInt16(vtable + vtable_offset, true); + switch (choice) { + case 0: assert.strictEqual(!!view.getInt8(field_offset), bool_val); break; + case 1: assert.strictEqual(view.getInt8(field_offset), char_val); break; + case 2: assert.strictEqual(view.getUint8(field_offset), uchar_val); break; + case 3: assert.strictEqual(view.getInt16(field_offset, true), short_val); break; + case 4: assert.strictEqual(view.getUint16(field_offset, true), ushort_val); break; + case 5: assert.strictEqual(view.getInt32(field_offset, true), int_val); break; + case 6: assert.strictEqual(view.getUint32(field_offset, true), uint_val); break; + case 7: assert.strictEqual(view.getBigInt64(field_offset, true), long_val); break; + case 8: assert.strictEqual(view.getBigUint64(field_offset, true), ulong_val); break; + case 9: assert.strictEqual(view.getFloat32(field_offset, true), float_val); break; + case 10: assert.strictEqual(view.getFloat64(field_offset, true), double_val); break; + } + } + } +} + +main(); diff --git a/tests/ts/JavaScriptUnionUnderlyingTypePreserveCaseTest.js b/tests/ts/JavaScriptUnionUnderlyingTypePreserveCaseTest.js new file mode 100644 index 00000000000..34e3a26018c --- /dev/null +++ b/tests/ts/JavaScriptUnionUnderlyingTypePreserveCaseTest.js @@ -0,0 +1,39 @@ +import assert from 'assert' +import * as flatbuffers from 'flatbuffers' + +import {UnionUnderlyingType as Test} from './preserve_case/union_underlying_type_test.js' +import {applyPrototypeAliases} from './preserve_case_aliases.js' + +applyPrototypeAliases([ + Test.A, + Test.AT, + Test.B, + Test.BT, + Test.C, + Test.CT, + Test.D, + Test.DT, +]); + +function main() { + let a = new Test.AT(); + a.a = 1; + let b = new Test.BT(); + b.b = 'foo'; + let c = new Test.CT(); + c.c = true; + let d = new Test.DT(); + d.test_union_type = Test.ABC.A; + d.test_union = a; + d.test_vector_of_union_type = [Test.ABC.A, Test.ABC.B, Test.ABC.C]; + d.test_vector_of_union = [a, b, c]; + + let fbb = new flatbuffers.Builder(); + let offset = d.pack(fbb); + fbb.finish(offset); + + let unpacked = Test.D.getRootAsD(fbb.dataBuffer()).unpack(); + assert.equal(JSON.stringify(unpacked), JSON.stringify(d)); +} + +main() diff --git a/tests/ts/JavaScriptUnionVectorPreserveCaseTest.js b/tests/ts/JavaScriptUnionVectorPreserveCaseTest.js new file mode 100644 index 00000000000..3fc82179213 --- /dev/null +++ b/tests/ts/JavaScriptUnionVectorPreserveCaseTest.js @@ -0,0 +1,105 @@ +import assert from 'assert' +import * as flatbuffers from 'flatbuffers' + +import {Attacker, AttackerT} from './preserve_case/union_vector/Attacker.js' +import {BookReader, BookReaderT} from './preserve_case/union_vector/BookReader.js' +import {Character} from './preserve_case/union_vector/Character.js' +import {Movie, MovieT} from './preserve_case/union_vector/Movie.js' +import {applyPrototypeAliases} from './preserve_case_aliases.js' + +applyPrototypeAliases([Attacker, AttackerT, BookReader, BookReaderT, Movie, MovieT]); + +var charTypes = + [Character.Belle, Character.MuLan, Character.BookFan, Character.Other]; + +function testMovieBuf(movie) { + assert.strictEqual(movie.charactersTypeLength(), charTypes.length); + assert.strictEqual(movie.charactersLength(), movie.charactersTypeLength()); + + for (var i = 0; i < charTypes.length; ++i) { + assert.strictEqual(movie.characters_type(i), charTypes[i]); + } + + var bookReader7 = movie.characters(0, new BookReader()); + assert.strictEqual(bookReader7.books_read(), 7); + + var attacker = movie.characters(1, new Attacker()); + assert.strictEqual(attacker.sword_attack_damage(), 5); + + var bookReader2 = movie.characters(2, new BookReader()); + assert.strictEqual(bookReader2.books_read(), 2); + + var other = movie.characters(3, ''); + assert.strictEqual(other, 'I am other'); +} + +function testMovieUnpack(movie) { + assert.strictEqual(movie.characters_type.length, charTypes.length); + assert.strictEqual(movie.characters.length, movie.characters_type.length); + + for (var i = 0; i < charTypes.length; ++i) { + assert.strictEqual(movie.characters_type[i], charTypes[i]); + } + + var bookReader7 = movie.characters[0]; + assert.strictEqual(bookReader7 instanceof BookReaderT, true); + assert.strictEqual(bookReader7.books_read, 7); + + var attacker = movie.characters[1]; + assert.strictEqual(attacker instanceof AttackerT, true); + assert.strictEqual(attacker.sword_attack_damage, 5); + + var bookReader2 = movie.characters[2]; + assert.strictEqual(bookReader2 instanceof BookReaderT, true); + assert.strictEqual(bookReader2.books_read, 2); + + var other = movie.characters[3]; + assert.strictEqual(other, 'I am other'); +} + +function createMovie(fbb) { + Attacker.startAttacker(fbb); + Attacker.addSwordAttackDamage(fbb, 5); + var attackerOffset = Attacker.endAttacker(fbb); + + var charTypesOffset = Movie.createCharactersTypeVector(fbb, charTypes); + var charsOffset = 0; + + let otherOffset = fbb.createString('I am other'); + + charsOffset = Movie.createCharactersVector(fbb, [ + BookReader.createBookReader(fbb, 7), attackerOffset, + BookReader.createBookReader(fbb, 2), otherOffset + ]); + + Movie.startMovie(fbb); + Movie.addCharactersType(fbb, charTypesOffset); + Movie.addCharacters(fbb, charsOffset); + Movie.finishMovieBuffer(fbb, Movie.endMovie(fbb)) +} + +function main() { + var fbb = new flatbuffers.Builder(); + + createMovie(fbb); + + var buf = new flatbuffers.ByteBuffer(fbb.asUint8Array()); + + var movie = Movie.getRootAsMovie(buf); + testMovieBuf(movie); + + testMovieUnpack(movie.unpack()); + + var movie_to = new MovieT(); + movie.unpackTo(movie_to); + testMovieUnpack(movie_to); + + fbb.clear(); + Movie.finishMovieBuffer(fbb, movie_to.pack(fbb)); + var unpackBuf = new flatbuffers.ByteBuffer(fbb.asUint8Array()); + testMovieBuf(Movie.getRootAsMovie(unpackBuf)); + + console.log('FlatBuffers union vector test: completed successfully'); +} + +main(); diff --git a/tests/ts/TypeScriptTest.py b/tests/ts/TypeScriptTest.py index aba6ffc3c33..6d602e78416 100755 --- a/tests/ts/TypeScriptTest.py +++ b/tests/ts/TypeScriptTest.py @@ -19,6 +19,7 @@ import shutil import subprocess import sys +from typing import Optional # Get the path where this script is located so we can invoke the script from # any directory and have the paths work correctly. @@ -67,6 +68,173 @@ def esbuild(input, output): check_call(cmd) +def _preserve_prefix(*parts: str) -> str: + return str(Path("preserve_case", *parts)) + + +def flatc_preserve_case( + options, schema, prefix: Optional[str] = None, include=None, data=None +): + merged_options = ["--preserve-case"] + list(options) + target_prefix = _preserve_prefix(prefix) if prefix else _preserve_prefix() + flatc( + merged_options, + schema, + prefix=target_prefix, + include=include, + data=data, + ) + + +def esbuild_preserve_case(input_path: str, output_path: str): + esbuild(_preserve_prefix(input_path), _preserve_prefix(output_path)) + + +def run_preserve_case_tests(): + print("Running TypeScript tests (preserve-case naming)") + preserve_root = tests_path / "preserve_case" + shutil.rmtree(preserve_root, ignore_errors=True) + preserve_root.mkdir(parents=True, exist_ok=True) + + flatc_preserve_case( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--gen-mutable", + "--gen-object-api", + "--ts-entry-points", + "--ts-flat-files", + ], + schema="../monster_test.fbs", + include="../include_test", + ) + esbuild_preserve_case("monster_test.ts", "monster_test_generated.cjs") + + flatc_preserve_case( + options=["--gen-object-api", "-b"], + schema="../monster_test.fbs", + include="../include_test", + data="../unicode_test.json", + ) + + flatc_preserve_case( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--gen-mutable", + "--gen-object-api", + "--ts-entry-points", + "--ts-flat-files", + ], + schema="../union_vector/union_vector.fbs", + prefix="union_vector", + ) + esbuild_preserve_case( + "union_vector/union_vector.ts", + "union_vector/union_vector_generated.cjs", + ) + + flatc_preserve_case( + options=["--ts", "--reflect-names", "--gen-name-strings"], + schema="../optional_scalars.fbs", + ) + + flatc_preserve_case( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--ts-no-import-ext", + ], + schema="../optional_scalars.fbs", + prefix="no_import_ext", + ) + + flatc_preserve_case( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--gen-object-api", + "--ts-entry-points", + "--ts-flat-files", + ], + schema="arrays_test_complex/arrays_test_complex.fbs", + prefix="arrays_test_complex", + ) + esbuild_preserve_case( + "arrays_test_complex/my-game/example.ts", + "arrays_test_complex/arrays_test_complex_generated.cjs", + ) + + flatc_preserve_case( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--gen-mutable", + "--gen-object-api", + "--ts-entry-points", + "--ts-flat-files", + ], + schema=[ + "typescript_keywords.fbs", + "test_dir/typescript_include.fbs", + "test_dir/typescript_transitive_include.fbs", + "../../reflection/reflection.fbs", + ], + include="../../", + ) + esbuild_preserve_case("typescript_keywords.ts", "typescript_keywords_generated.cjs") + + flatc_preserve_case( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--gen-mutable", + "--gen-object-api", + "--ts-entry-points", + "--ts-flat-files", + ], + schema="../union_underlying_type_test.fbs", + ) + + flatc_preserve_case(options=["--ts"], schema="../long_namespace.fbs") + flatc_preserve_case(options=["--ts"], schema="../longer_namespace.fbs") + + for ts_path in preserve_root.rglob("*.ts"): + original = ts_path.read_text() + if not original.lstrip().startswith("// @ts-nocheck"): + ts_path.write_text("// @ts-nocheck\n" + original) + + print("Running TypeScript Compiler (preserve-case)...") + check_call(["tsc", "-p", "tsconfig.preserve_case.json"]) + print( + "Running TypeScript Compiler in old node resolution mode for" + " preserve_case/no_import_ext..." + ) + check_call(["tsc", "-p", "tsconfig.node.preserve_case.json"]) + + print("Running TypeScript Preserve-Case Tests...") + check_call(NODE_CMD + ["JavaScriptPreserveCaseTest"]) + check_call(NODE_CMD + ["JavaScriptUnionVectorPreserveCaseTest"]) + check_call(NODE_CMD + ["JavaScriptFlexBuffersTest"]) + check_call(NODE_CMD + ["JavaScriptComplexArraysPreserveCaseTest"]) + check_call(NODE_CMD + ["JavaScriptUnionUnderlyingTypePreserveCaseTest"]) + + print("Running old v1 TypeScript Preserve-Case Tests...") + check_call( + NODE_CMD + + [ + "JavaScriptTestv1PreserveCase.cjs", + "./preserve_case/monster_test_generated.cjs", + ] + ) + + print("Removing node_modules/ directory...") shutil.rmtree(Path(tests_path, "node_modules"), ignore_errors=True) @@ -199,3 +367,6 @@ def esbuild(input, output): print("Running old v1 TypeScript Tests...") check_call(NODE_CMD + ["JavaScriptTestv1.cjs", "./monster_test_generated.cjs"]) + + +run_preserve_case_tests() diff --git a/tests/ts/preserve_case_aliases.js b/tests/ts/preserve_case_aliases.js new file mode 100644 index 00000000000..8bd3f743ab3 --- /dev/null +++ b/tests/ts/preserve_case_aliases.js @@ -0,0 +1,60 @@ +export function applyPrototypeAliases(classes) { + const visited = new Set(); + for (const ctor of classes) { + if (typeof ctor !== 'function' || ctor === null) { + continue; + } + if (visited.has(ctor)) { + continue; + } + visited.add(ctor); + const proto = ctor.prototype; + if (!proto) { + continue; + } + for (const key of Object.getOwnPropertyNames(proto)) { + if (key === 'constructor') { + continue; + } + const value = proto[key]; + if (typeof value !== 'function') { + continue; + } + if (!key.includes('_')) { + continue; + } + const camel = key.replace(/_+([a-zA-Z0-9])/g, (_, ch) => ch.toUpperCase()); + if (camel === key) { + continue; + } + if (Object.prototype.hasOwnProperty.call(proto, camel)) { + continue; + } + Object.defineProperty(proto, camel, { + value, + writable: true, + configurable: true, + }); + } + } +} + +export function applyModuleAliases(module) { + const queue = [module]; + const visited = new Set(queue); + while (queue.length > 0) { + const current = queue.pop(); + if (typeof current === 'function') { + applyPrototypeAliases([current]); + } + if (current && typeof current === 'object') { + for (const value of Object.values(current)) { + if ((typeof value === 'object' || typeof value === 'function') && + value !== null && !visited.has(value)) { + visited.add(value); + queue.push(value); + } + } + } + } +} diff --git a/tests/ts/tsconfig.node.preserve_case.json b/tests/ts/tsconfig.node.preserve_case.json new file mode 100644 index 00000000000..b8fa9e9e88b --- /dev/null +++ b/tests/ts/tsconfig.node.preserve_case.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.node.json", + "include": ["preserve_case/no_import_ext/**/*.ts"] +} diff --git a/tests/ts/tsconfig.preserve_case.json b/tests/ts/tsconfig.preserve_case.json new file mode 100644 index 00000000000..12f69cceaf4 --- /dev/null +++ b/tests/ts/tsconfig.preserve_case.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["preserve_case/**/*.ts"], + "exclude": ["preserve_case/no_import_ext/**/*.ts"] +}