#include "flatbuffers/grpc.h" #include "monster_test_generated.h" #include "test_assert.h" #include "test_builder.h" using MyGame::Example::Any_NONE; using MyGame::Example::CreateStat; using MyGame::Example::Vec3; bool verify(flatbuffers::grpc::Message &msg, const std::string &expected_name, Color expected_color) { const Monster *monster = msg.GetRoot(); const auto name = monster->name()->str(); const auto color = monster->color(); TEST_EQ(name, expected_name); TEST_EQ(color, expected_color); return (name == expected_name) && (color == expected_color); } bool release_n_verify(flatbuffers::grpc::MessageBuilder &mbb, const std::string &expected_name, Color expected_color) { flatbuffers::grpc::Message msg = mbb.ReleaseMessage(); return verify(msg, expected_name, expected_color); } void builder_move_assign_after_releaseraw_test( flatbuffers::grpc::MessageBuilder dst) { auto root_offset1 = populate1(dst); dst.Finish(root_offset1); size_t size, offset; grpc_slice slice; dst.ReleaseRaw(size, offset, slice); flatbuffers::FlatBufferBuilder src; auto root_offset2 = populate2(src); src.Finish(root_offset2); auto src_size = src.GetSize(); // Move into a released builder. dst = std::move(src); TEST_EQ(dst.GetSize(), src_size); TEST_ASSERT(release_n_verify(dst, m2_name(), m2_color())); TEST_EQ(src.GetSize(), 0); grpc_slice_unref(slice); } template struct BuilderReuseTests { static void builder_reusable_after_release_message_test( TestSelector selector) { if (!selector.count(REUSABLE_AFTER_RELEASE_MESSAGE)) { return; } flatbuffers::grpc::MessageBuilder mb; std::vector> buffers; for (int i = 0; i < 5; ++i) { auto root_offset1 = populate1(mb); mb.Finish(root_offset1); buffers.push_back(mb.ReleaseMessage()); TEST_ASSERT_FUNC(verify(buffers[i], m1_name(), m1_color())); } } static void builder_reusable_after_release_test(TestSelector selector) { if (!selector.count(REUSABLE_AFTER_RELEASE)) { return; } // FIXME: Populate-Release loop fails assert(GRPC_SLICE_IS_EMPTY(slice_)) in // SliceAllocator::allocate in the second iteration. flatbuffers::grpc::MessageBuilder mb; std::vector buffers; for (int i = 0; i < 2; ++i) { auto root_offset1 = populate1(mb); mb.Finish(root_offset1); buffers.push_back(mb.Release()); TEST_ASSERT_FUNC(verify(buffers[i], m1_name(), m1_color())); } } static void builder_reusable_after_releaseraw_test(TestSelector selector) { if (!selector.count(REUSABLE_AFTER_RELEASE_RAW)) { return; } flatbuffers::grpc::MessageBuilder mb; for (int i = 0; i < 5; ++i) { auto root_offset1 = populate1(mb); mb.Finish(root_offset1); size_t size, offset; grpc_slice slice; const uint8_t *buf = mb.ReleaseRaw(size, offset, slice); TEST_ASSERT_FUNC(verify(buf, offset, m1_name(), m1_color())); grpc_slice_unref(slice); } } static void builder_reusable_after_release_and_move_assign_test( TestSelector selector) { if (!selector.count(REUSABLE_AFTER_RELEASE_AND_MOVE_ASSIGN)) { return; } // FIXME: Release-move_assign loop fails assert(p == // GRPC_SLICE_START_PTR(slice_)) in DetachedBuffer destructor after all the // iterations flatbuffers::grpc::MessageBuilder dst; std::vector buffers; for (int i = 0; i < 2; ++i) { auto root_offset1 = populate1(dst); dst.Finish(root_offset1); buffers.push_back(dst.Release()); TEST_ASSERT_FUNC(verify(buffers[i], m1_name(), m1_color())); // bring dst back to life. SrcBuilder src; dst = std::move(src); TEST_EQ_FUNC(dst.GetSize(), 0); TEST_EQ_FUNC(src.GetSize(), 0); } } static void builder_reusable_after_release_message_and_move_assign_test( TestSelector selector) { if (!selector.count(REUSABLE_AFTER_RELEASE_MESSAGE_AND_MOVE_ASSIGN)) { return; } flatbuffers::grpc::MessageBuilder dst; std::vector> buffers; for (int i = 0; i < 5; ++i) { auto root_offset1 = populate1(dst); dst.Finish(root_offset1); buffers.push_back(dst.ReleaseMessage()); TEST_ASSERT_FUNC(verify(buffers[i], m1_name(), m1_color())); // bring dst back to life. SrcBuilder src; dst = std::move(src); TEST_EQ_FUNC(dst.GetSize(), 0); TEST_EQ_FUNC(src.GetSize(), 0); } } static void builder_reusable_after_releaseraw_and_move_assign_test( TestSelector selector) { if (!selector.count(REUSABLE_AFTER_RELEASE_RAW_AND_MOVE_ASSIGN)) { return; } flatbuffers::grpc::MessageBuilder dst; for (int i = 0; i < 5; ++i) { auto root_offset1 = populate1(dst); dst.Finish(root_offset1); size_t size, offset; grpc_slice slice = grpc_empty_slice(); const uint8_t *buf = dst.ReleaseRaw(size, offset, slice); TEST_ASSERT_FUNC(verify(buf, offset, m1_name(), m1_color())); grpc_slice_unref(slice); SrcBuilder src; dst = std::move(src); TEST_EQ_FUNC(dst.GetSize(), 0); TEST_EQ_FUNC(src.GetSize(), 0); } } static void run_tests(TestSelector selector) { builder_reusable_after_release_test(selector); builder_reusable_after_release_message_test(selector); builder_reusable_after_releaseraw_test(selector); builder_reusable_after_release_and_move_assign_test(selector); builder_reusable_after_releaseraw_and_move_assign_test(selector); builder_reusable_after_release_message_and_move_assign_test(selector); } }; void slice_allocator_tests() { // move-construct no-delete test { size_t size = 2048; flatbuffers::grpc::SliceAllocator sa1; uint8_t *buf = sa1.allocate(size); TEST_ASSERT_FUNC(buf != 0); buf[0] = 100; buf[size - 1] = 200; flatbuffers::grpc::SliceAllocator sa2(std::move(sa1)); // buf should not be deleted after move-construct TEST_EQ_FUNC(buf[0], 100); TEST_EQ_FUNC(buf[size - 1], 200); // buf is freed here } // move-assign test { flatbuffers::grpc::SliceAllocator sa1, sa2; uint8_t *buf = sa1.allocate(2048); sa1 = std::move(sa2); // sa1 deletes previously allocated memory in move-assign. // So buf is no longer usable here. TEST_ASSERT_FUNC(buf != 0); } } /// This function does not populate exactly the first half of the table. But it /// could. void populate_first_half(MyGame::Example::MonsterBuilder &wrapper, flatbuffers::Offset name_offset) { wrapper.add_name(name_offset); wrapper.add_color(m1_color()); } /// This function does not populate exactly the second half of the table. But it /// could. void populate_second_half(MyGame::Example::MonsterBuilder &wrapper) { wrapper.add_hp(77); wrapper.add_mana(88); Vec3 vec3; wrapper.add_pos(&vec3); } /// This function is a hack to update the FlatBufferBuilder reference (fbb_) in /// the MonsterBuilder object. This function will break if fbb_ is not the first /// member in MonsterBuilder. In that case, some offset must be added. This /// function is used exclusively for testing correctness of move operations /// between FlatBufferBuilders. If MonsterBuilder had a fbb_ pointer, this hack /// would be unnecessary. That involves a code-generator change though. void test_only_hack_update_fbb_reference( MyGame::Example::MonsterBuilder &monsterBuilder, flatbuffers::grpc::MessageBuilder &mb) { *reinterpret_cast(&monsterBuilder) = &mb; } /// This test validates correctness of move conversion of FlatBufferBuilder to a /// MessageBuilder DURING a table construction. Half of the table is constructed /// using FlatBufferBuilder and the other half of the table is constructed using /// a MessageBuilder. void builder_move_ctor_conversion_before_finish_half_n_half_table_test() { for (size_t initial_size = 4; initial_size <= 2048; initial_size *= 2) { flatbuffers::FlatBufferBuilder fbb(initial_size); auto name_offset = fbb.CreateString(m1_name()); MyGame::Example::MonsterBuilder monsterBuilder( fbb); // starts a table in FlatBufferBuilder populate_first_half(monsterBuilder, name_offset); flatbuffers::grpc::MessageBuilder mb(std::move(fbb)); test_only_hack_update_fbb_reference(monsterBuilder, mb); // hack populate_second_half(monsterBuilder); mb.Finish(monsterBuilder.Finish()); // ends the table in MessageBuilder TEST_ASSERT_FUNC(release_n_verify(mb, m1_name(), m1_color())); TEST_EQ_FUNC(fbb.GetSize(), 0); } } /// This test populates a COMPLETE inner table before move conversion and later /// populates more members in the outer table. void builder_move_ctor_conversion_before_finish_test() { for (size_t initial_size = 1; initial_size <= 2048; initial_size += 1) { flatbuffers::FlatBufferBuilder fbb(initial_size); auto stat_offset = CreateStat(fbb, fbb.CreateString("SomeId"), 0, 0); flatbuffers::grpc::MessageBuilder mb(std::move(fbb)); auto monster_offset = CreateMonster(mb, 0, 150, 100, mb.CreateString(m1_name()), 0, m1_color(), Any_NONE, 0, 0, 0, 0, 0, 0, stat_offset); mb.Finish(monster_offset); { auto mon = flatbuffers::GetRoot(mb.GetBufferPointer()); TEST_NOTNULL(mon); TEST_NOTNULL(mon->name()); TEST_EQ_STR(mon->name()->c_str(), m1_name().c_str()); TEST_EQ(mon->color(), m1_color()); } TEST_EQ(1, MyGame::Example::Color_Red); TEST_EQ(1, m1_color()); TEST_ASSERT_FUNC(release_n_verify(mb, m1_name(), m1_color())); TEST_EQ_FUNC(fbb.GetSize(), 0); } } /// This test validates correctness of move conversion of FlatBufferBuilder to a /// MessageBuilder DURING a table construction. Half of the table is constructed /// using FlatBufferBuilder and the other half of the table is constructed using /// a MessageBuilder. void builder_move_assign_conversion_before_finish_half_n_half_table_test() { flatbuffers::FlatBufferBuilder fbb; flatbuffers::grpc::MessageBuilder mb; for (int i = 0; i < 5; ++i) { flatbuffers::FlatBufferBuilder fbb; auto name_offset = fbb.CreateString(m1_name()); MyGame::Example::MonsterBuilder monsterBuilder( fbb); // starts a table in FlatBufferBuilder populate_first_half(monsterBuilder, name_offset); mb = std::move(fbb); test_only_hack_update_fbb_reference(monsterBuilder, mb); // hack populate_second_half(monsterBuilder); mb.Finish(monsterBuilder.Finish()); // ends the table in MessageBuilder TEST_ASSERT_FUNC(release_n_verify(mb, m1_name(), m1_color())); TEST_EQ_FUNC(fbb.GetSize(), 0); } } /// This test populates a COMPLETE inner table before move conversion and later /// populates more members in the outer table. void builder_move_assign_conversion_before_finish_test() { flatbuffers::FlatBufferBuilder fbb; flatbuffers::grpc::MessageBuilder mb; for (int i = 0; i < 5; ++i) { auto stat_offset = CreateStat(fbb, fbb.CreateString("SomeId"), 0, 0); mb = std::move(fbb); auto monster_offset = CreateMonster(mb, 0, 150, 100, mb.CreateString(m1_name()), 0, m1_color(), Any_NONE, 0, 0, 0, 0, 0, 0, stat_offset); mb.Finish(monster_offset); TEST_ASSERT_FUNC(release_n_verify(mb, m1_name(), m1_color())); TEST_EQ_FUNC(fbb.GetSize(), 0); } } /// This test populates data, finishes the buffer, and does move conversion /// after. void builder_move_ctor_conversion_after_finish_test() { flatbuffers::FlatBufferBuilder fbb; fbb.Finish(populate1(fbb)); flatbuffers::grpc::MessageBuilder mb(std::move(fbb)); TEST_ASSERT_FUNC(release_n_verify(mb, m1_name(), m1_color())); TEST_EQ_FUNC(fbb.GetSize(), 0); } /// This test populates data, finishes the buffer, and does move conversion /// after. void builder_move_assign_conversion_after_finish_test() { flatbuffers::FlatBufferBuilder fbb; flatbuffers::grpc::MessageBuilder mb; for (int i = 0; i < 5; ++i) { fbb.Finish(populate1(fbb)); mb = std::move(fbb); TEST_ASSERT_FUNC(release_n_verify(mb, m1_name(), m1_color())); TEST_EQ_FUNC(fbb.GetSize(), 0); } } void message_builder_tests() { using flatbuffers::FlatBufferBuilder; using flatbuffers::grpc::MessageBuilder; slice_allocator_tests(); #ifndef __APPLE__ builder_move_ctor_conversion_before_finish_half_n_half_table_test(); builder_move_assign_conversion_before_finish_half_n_half_table_test(); #endif // __APPLE__ builder_move_ctor_conversion_before_finish_test(); builder_move_assign_conversion_before_finish_test(); builder_move_ctor_conversion_after_finish_test(); builder_move_assign_conversion_after_finish_test(); BuilderTests::all_tests(); BuilderTests::all_tests(); BuilderReuseTestSelector tests[6] = { // REUSABLE_AFTER_RELEASE, // Assertion failed: // (GRPC_SLICE_IS_EMPTY(slice_)) // REUSABLE_AFTER_RELEASE_AND_MOVE_ASSIGN, // Assertion failed: (p == // GRPC_SLICE_START_PTR(slice_) REUSABLE_AFTER_RELEASE_RAW, REUSABLE_AFTER_RELEASE_MESSAGE, REUSABLE_AFTER_RELEASE_MESSAGE_AND_MOVE_ASSIGN, REUSABLE_AFTER_RELEASE_RAW_AND_MOVE_ASSIGN }; BuilderReuseTests::run_tests( TestSelector(tests, tests + 6)); BuilderReuseTests::run_tests( TestSelector(tests, tests + 6)); }