diff --git a/BUILD b/BUILD index 1a3ff58b4..cb78e37eb 100644 --- a/BUILD +++ b/BUILD @@ -122,6 +122,10 @@ cc_test( "tests/namespace_test/namespace_test1_generated.h", "tests/namespace_test/namespace_test2_generated.h", "tests/test.cpp", + "tests/test_builder.h", + "tests/test_assert.h", + "tests/test_builder.cpp", + "tests/test_assert.cpp", "tests/union_vector/union_vector_generated.h", ":public_headers", ], diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d224fe27..cb4453fa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,10 @@ set(FlatBuffers_Tests_SRCS ${FlatBuffers_Library_SRCS} src/idl_gen_fbs.cpp tests/test.cpp + tests/test_assert.h + tests/test_assert.cpp + tests/test_builder.h + tests/test_builder.cpp # file generate by running compiler on tests/monster_test.fbs ${CMAKE_CURRENT_BINARY_DIR}/tests/monster_test_generated.h ) @@ -100,7 +104,11 @@ set(FlatBuffers_GRPCTest_SRCS include/flatbuffers/flatbuffers.h include/flatbuffers/grpc.h tests/monster_test.grpc.fb.h + tests/test_assert.h + tests/test_builder.h tests/monster_test.grpc.fb.cc + tests/test_assert.cpp + tests/test_builder.cpp grpc/tests/grpctest.cpp grpc/tests/message_builder_test.cpp # file generated by running compiler on samples/monster.fbs diff --git a/android/jni/Android.mk b/android/jni/Android.mk index aec561a95..b22033269 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -47,6 +47,10 @@ include $(CLEAR_VARS) LOCAL_MODULE := FlatBufferTest LOCAL_SRC_FILES := android/jni/main.cpp \ tests/test.cpp \ + tests/test_assert.h \ + tests/test_builder.h \ + tests/test_assert.cpp \ + tests/test_builder.cpp \ src/idl_gen_fbs.cpp \ src/idl_gen_general.cpp LOCAL_LDLIBS := -llog -landroid -latomic diff --git a/grpc/tests/grpctest.cpp b/grpc/tests/grpctest.cpp index cb4326ecd..50b17cc0c 100644 --- a/grpc/tests/grpctest.cpp +++ b/grpc/tests/grpctest.cpp @@ -20,9 +20,10 @@ #include "monster_test.grpc.fb.h" #include "monster_test_generated.h" +#include "test_assert.h" using namespace MyGame::Example; -int builder_tests(); +void message_builder_tests(); // The callback implementation of our server, that derives from the generated // code. It implements all rpcs specified in the FlatBuffers schema. @@ -166,6 +167,15 @@ int grpc_server_test() { } int main(int /*argc*/, const char * /*argv*/ []) { - return builder_tests() + grpc_server_test(); + message_builder_tests(); + grpc_server_test(); + + if (!testing_fails) { + TEST_OUTPUT_LINE("ALL TESTS PASSED"); + return 0; + } else { + TEST_OUTPUT_LINE("%d FAILED TESTS", testing_fails); + return 1; + } } diff --git a/grpc/tests/message_builder_test.cpp b/grpc/tests/message_builder_test.cpp index 974e050c9..25f04da82 100644 --- a/grpc/tests/message_builder_test.cpp +++ b/grpc/tests/message_builder_test.cpp @@ -1,35 +1,10 @@ #include "flatbuffers/grpc.h" #include "monster_test_generated.h" +#include "test_assert.h" +#include "test_builder.h" -static int builder_test_error = 0; - -#define test_assert(condition) do { \ - if(!(condition)) { \ - fprintf(stderr, "%s:%d: %s failed.\n", __FILE__, __LINE__, #condition);\ - builder_test_error = 1;\ - } \ -} while(0) - -using namespace MyGame::Example; - -const std::string m1_name = "Cyberdemon"; -const Color m1_color = Color_Red; -const std::string m2_name = "Imp"; -const Color m2_color = Color_Green; - -flatbuffers::Offset populate1(flatbuffers::FlatBufferBuilder &builder) { - auto name_offset = builder.CreateString(m1_name); - return CreateMonster(builder, nullptr, 0, 0, name_offset, 0, m1_color); -} - -flatbuffers::Offset populate2(flatbuffers::FlatBufferBuilder &builder) { - auto name_offset = builder.CreateString(m2_name); - return CreateMonster(builder, nullptr, 0, 0, name_offset, 0, m2_color); -} - -bool release_n_verify(flatbuffers::FlatBufferBuilder &fbb, const std::string &expected_name, Color color) { - flatbuffers::DetachedBuffer buf = fbb.Release(); - const Monster *monster = flatbuffers::GetRoot(buf.data()); +bool verify(flatbuffers::grpc::Message &msg, const std::string &expected_name, Color color) { + const Monster *monster = msg.GetRoot(); return (monster->name()->str() == expected_name) && (monster->color() == color); } @@ -39,121 +14,175 @@ bool release_n_verify(flatbuffers::grpc::MessageBuilder &mbb, const std::string return (monster->name()->str() == expected_name) && (monster->color() == color); } -struct OwnedAllocator : public flatbuffers::DefaultAllocator {}; +template <> +struct BuilderReuseTests { + static void builder_reusable_after_release_message_test(TestSelector selector) { + if (!selector.count(REUSABLE_AFTER_RELEASE_MESSAGE)) { + return; + } -struct TestHeapMessageBuilder : public flatbuffers::FlatBufferBuilder { - TestHeapMessageBuilder() - : flatbuffers::FlatBufferBuilder(2048, new OwnedAllocator(), true) {} -}; - -template -struct BuilderTests { - static void empty_builder_movector_test() { - Builder b1; - size_t b1_size = b1.GetSize(); - Builder b2(std::move(b1)); - size_t b2_size = b2.GetSize(); - test_assert(b1_size == 0); - test_assert(b1_size == b2_size); + flatbuffers::grpc::MessageBuilder b1; + std::vector> buffers; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + buffers.push_back(b1.ReleaseMessage()); + TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color)); + } } - static void nonempty_builder_movector_test() { - Builder b1; - populate1(b1); - size_t b1_size = b1.GetSize(); - Builder b2(std::move(b1)); - test_assert(b1_size == b2.GetSize()); - test_assert(0 == b1.GetSize()); + 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_)). + + flatbuffers::grpc::MessageBuilder b1; + std::vector buffers; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + buffers.push_back(b1.Release()); + TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color)); + } } - static void builder_movector_before_finish_test() { - Builder b1; - auto root_offset1 = populate1(b1); - Builder b2(std::move(b1)); - b2.Finish(root_offset1); - test_assert(release_n_verify(b2, m1_name, m1_color)); - test_assert(0 == b1.GetSize()); + static void builder_reusable_after_releaseraw_test(TestSelector selector) { + if (!selector.count(REUSABLE_AFTER_RELEASE_RAW)) { + return; + } + + flatbuffers::grpc::MessageBuilder b1; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + size_t size, offset; + grpc_slice slice; + const uint8_t *buf = b1.ReleaseRaw(size, offset, slice); + TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color)); + grpc_slice_unref(slice); + } } - static void builder_movector_after_finish_test() { - Builder b1; - auto root_offset1 = populate1(b1); - b1.Finish(root_offset1); - Builder b2(std::move(b1)); - test_assert(release_n_verify(b2, m1_name, m1_color)); - test_assert(0 == b1.GetSize()); + 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_)). + + flatbuffers::grpc::MessageBuilder b1; + std::vector buffers; + + for (int i = 0; i < 1; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + buffers.push_back(b1.Release()); + TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color)); + + // bring b1 back to life. + flatbuffers::grpc::MessageBuilder b2; + b1 = std::move(b2); + TEST_EQ_FUNC(b1.GetSize(), 0); + TEST_EQ_FUNC(b2.GetSize(), 0); + } } - static void builder_move_assign_before_finish_test() { - Builder b1; - auto root_offset1 = populate1(b1); - Builder b2; - populate2(b2); - b2 = std::move(b1); - b2.Finish(root_offset1); - test_assert(release_n_verify(b2, m1_name, m1_color)); - test_assert(0 == b1.GetSize()); + 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 b1; + std::vector> buffers; + + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + buffers.push_back(b1.ReleaseMessage()); + TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color)); + + // bring b1 back to life. + flatbuffers::grpc::MessageBuilder b2; + b1 = std::move(b2); + TEST_EQ_FUNC(b1.GetSize(), 0); + TEST_EQ_FUNC(b2.GetSize(), 0); + } } - static void builder_move_assign_after_finish_test() { - Builder b1; - auto root_offset1 = populate1(b1); - b1.Finish(root_offset1); - Builder b2; - auto root_offset2 = populate2(b2); - b2.Finish(root_offset2); - b2 = std::move(b1); - test_assert(release_n_verify(b2, m1_name, m1_color)); - test_assert(0 == b1.GetSize()); + 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 b1; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + size_t size, offset; + grpc_slice slice = grpc_empty_slice(); + const uint8_t *buf = b1.ReleaseRaw(size, offset, slice); + TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color)); + grpc_slice_unref(slice); + + flatbuffers::grpc::MessageBuilder b2; + b1 = std::move(b2); + TEST_EQ_FUNC(b1.GetSize(), 0); + TEST_EQ_FUNC(b2.GetSize(), 0); + } } - static void builder_swap_before_finish_test() { - Builder b1; - auto root_offset1 = populate1(b1); - auto size1 = b1.GetSize(); - Builder b2; - auto root_offset2 = populate2(b2); - auto size2 = b2.GetSize(); - b1.Swap(b2); - b1.Finish(root_offset2); - b2.Finish(root_offset1); - test_assert(b1.GetSize() > size2); - test_assert(b2.GetSize() > size1); - test_assert(release_n_verify(b1, m2_name, m2_color)); - test_assert(release_n_verify(b2, m1_name, m1_color)); - } - - static void builder_swap_after_finish_test() { - Builder b1; - auto root_offset1 = populate1(b1); - b1.Finish(root_offset1); - auto size1 = b1.GetSize(); - Builder b2; - auto root_offset2 = populate2(b2); - b2.Finish(root_offset2); - auto size2 = b2.GetSize(); - b1.Swap(b2); - test_assert(b1.GetSize() == size2); - test_assert(b2.GetSize() == size1); - test_assert(release_n_verify(b1, m2_name, m2_color)); - test_assert(release_n_verify(b2, m1_name, m1_color)); - } - - static void all_tests() { - empty_builder_movector_test(); - nonempty_builder_movector_test(); - builder_movector_before_finish_test(); - builder_movector_after_finish_test(); - builder_move_assign_before_finish_test(); - builder_move_assign_after_finish_test(); - builder_swap_before_finish_test(); - builder_swap_after_finish_test(); + 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); } }; -int builder_tests() { - BuilderTests::all_tests(); - BuilderTests::all_tests(); - BuilderTests::all_tests(); - return builder_test_error; +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 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); + } +} + +void message_builder_tests() { + slice_allocator_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)); } diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 386f2cfef..573b6a259 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -581,10 +581,10 @@ class vector_downward { buf_(other.buf_), cur_(other.cur_), scratch_(other.scratch_) { - other.allocator_ = nullptr; - other.own_allocator_ = false; + // No change in other.allocator_ // No change in other.initial_size_ // No change in other.buffer_minalign_ + other.own_allocator_ = false; other.reserved_ = 0; other.buf_ = nullptr; other.cur_ = nullptr; @@ -639,18 +639,22 @@ class vector_downward { allocated_bytes = reserved_; offset = static_cast(cur_ - buf_); + // release_raw only relinquishes the buffer ownership. + // Does not deallocate or reset the allocator. Destructor will do that. buf_ = nullptr; - clear_allocator(); clear(); return buf; } // Relinquish the pointer to the caller. DetachedBuffer release() { + // allocator ownership (if any) is transferred to DetachedBuffer. DetachedBuffer fb(allocator_, own_allocator_, buf_, reserved_, cur_, size()); - allocator_ = nullptr; - own_allocator_ = false; + if (own_allocator_) { + allocator_ = nullptr; + own_allocator_ = false; + } buf_ = nullptr; clear(); return fb; diff --git a/include/flatbuffers/grpc.h b/include/flatbuffers/grpc.h index 2bcfa44a1..00553ef64 100644 --- a/include/flatbuffers/grpc.h +++ b/include/flatbuffers/grpc.h @@ -89,13 +89,15 @@ class SliceAllocator : public Allocator { SliceAllocator &operator=(const SliceAllocator &other) = delete; SliceAllocator(SliceAllocator &&other) - : slice_(other.slice_) { - other.slice_ = grpc_empty_slice(); + : slice_(grpc_empty_slice()) { + // default-construct and swap idiom + swap(other); } SliceAllocator &operator=(SliceAllocator &&other) { - slice_ = other.slice_; - other.slice_ = grpc_empty_slice(); + // move-construct and swap idiom + SliceAllocator temp(std::move(other)); + swap(temp); return *this; } @@ -190,6 +192,16 @@ class MessageBuilder : private detail::SliceAllocatorMember, buf_.swap_allocator(other.buf_); } + // Releases the ownership of the buffer pointer. + // Returns the size, offset, and the original grpc_slice that + // allocated the buffer. Also see grpc_slice_unref(). + uint8_t *ReleaseRaw(size_t &size, size_t &offset, grpc_slice &slice) { + uint8_t *buf = FlatBufferBuilder::ReleaseRaw(size, offset); + slice = slice_allocator_.slice_; + slice_allocator_.slice_ = grpc_empty_slice(); + return buf; + } + ~MessageBuilder() {} // GetMessage extracts the subslice of the buffer corresponding to the diff --git a/tests/test.cpp b/tests/test.cpp index 0aefc5dce..4f89dd3de 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -33,6 +33,7 @@ #include "namespace_test/namespace_test1_generated.h" #include "namespace_test/namespace_test2_generated.h" #include "union_vector/union_vector_generated.h" +#include "test_assert.h" // clang-format off #ifndef FLATBUFFERS_CPP98_STL @@ -43,44 +44,7 @@ using namespace MyGame::Example; -#ifdef __ANDROID__ - #include - #define TEST_OUTPUT_LINE(...) \ - __android_log_print(ANDROID_LOG_INFO, "FlatBuffers", __VA_ARGS__) - #define FLATBUFFERS_NO_FILE_TESTS -#else - #define TEST_OUTPUT_LINE(...) \ - { printf(__VA_ARGS__); printf("\n"); } -#endif -// clang-format on - -int testing_fails = 0; - -void TestFail(const char *expval, const char *val, const char *exp, - const char *file, int line) { - TEST_OUTPUT_LINE("VALUE: \"%s\"", expval); - TEST_OUTPUT_LINE("EXPECTED: \"%s\"", val); - TEST_OUTPUT_LINE("TEST FAILED: %s:%d, %s", file, line, exp); - assert(0); - testing_fails++; -} - -void TestEqStr(const char *expval, const char *val, const char *exp, - const char *file, int line) { - if (strcmp(expval, val) != 0) { TestFail(expval, val, exp, file, line); } -} - -template -void TestEq(T expval, U val, const char *exp, const char *file, int line) { - if (U(expval) != val) { - TestFail(flatbuffers::NumToString(expval).c_str(), - flatbuffers::NumToString(val).c_str(), exp, file, line); - } -} - -#define TEST_EQ(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__) -#define TEST_NOTNULL(exp) TestEq(exp == NULL, false, #exp, __FILE__, __LINE__) -#define TEST_EQ_STR(exp, val) TestEqStr(exp, val, #exp, __FILE__, __LINE__) +void FlatBufferBuilderTest(); // Include simple random number generator to ensure results will be the // same cross platform. @@ -2041,7 +2005,7 @@ void LoadVerifyBinaryTest() { } } -int main(int /*argc*/, const char * /*argv*/ []) { +int FlatBufferTests() { // clang-format off #if defined(FLATBUFFERS_MEMORY_LEAK_TRACKING) && \ defined(_MSC_VER) && defined(_DEBUG) @@ -2115,6 +2079,14 @@ int main(int /*argc*/, const char * /*argv*/ []) { UninitializedVectorTest(); EqualOperatorTest(); + return 0; +} + +int main(int /*argc*/, const char * /*argv*/ []) { + + FlatBufferTests(); + FlatBufferBuilderTest(); + if (!testing_fails) { TEST_OUTPUT_LINE("ALL TESTS PASSED"); return 0; diff --git a/tests/test_assert.cpp b/tests/test_assert.cpp new file mode 100644 index 000000000..30ab77620 --- /dev/null +++ b/tests/test_assert.cpp @@ -0,0 +1,17 @@ +#include "test_assert.h" + +int testing_fails = 0; + +void TestFail(const char *expval, const char *val, const char *exp, + const char *file, int line, const char *func) { + TEST_OUTPUT_LINE("VALUE: \"%s\"", expval); + TEST_OUTPUT_LINE("EXPECTED: \"%s\"", val); + TEST_OUTPUT_LINE("TEST FAILED: %s:%d, %s in %s", file, line, exp, func? func : ""); + testing_fails++; +} + +void TestEqStr(const char *expval, const char *val, const char *exp, + const char *file, int line) { + if (strcmp(expval, val) != 0) { TestFail(expval, val, exp, file, line); } +} + diff --git a/tests/test_assert.h b/tests/test_assert.h new file mode 100644 index 000000000..5a1446684 --- /dev/null +++ b/tests/test_assert.h @@ -0,0 +1,46 @@ +#ifndef TEST_ASSERT_H +#define TEST_ASSERT_H + +#include "flatbuffers/flatbuffers.h" +#include "flatbuffers/util.h" + +#ifdef __ANDROID__ +#include + #define TEST_OUTPUT_LINE(...) \ + __android_log_print(ANDROID_LOG_INFO, "FlatBuffers", __VA_ARGS__) + #define FLATBUFFERS_NO_FILE_TESTS +#else +#define TEST_OUTPUT_LINE(...) \ + { printf(__VA_ARGS__); printf("\n"); } +#endif +// clang-format on + +extern int testing_fails; + +void TestFail(const char *expval, const char *val, const char *exp, + const char *file, int line, const char *func = 0); + +void TestEqStr(const char *expval, const char *val, const char *exp, + const char *file, int line); + +template +void TestEq(T expval, U val, const char *exp, const char *file, int line, const char *func = 0) { + if (U(expval) != val) { + TestFail(flatbuffers::NumToString(expval).c_str(), + flatbuffers::NumToString(val).c_str(), exp, file, line, func); + } +} + +#define TEST_EQ(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__) +#define TEST_ASSERT(exp) TestEq(exp, true, #exp, __FILE__, __LINE__) +#ifdef WIN32 + #define TEST_ASSERT_FUNC(exp) TestEq(exp, true, #exp, __FILE__, __LINE__, __FUNCTION__) + #define TEST_EQ_FUNC(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__, __FUNCTION__) +#else + #define TEST_ASSERT_FUNC(exp) TestEq(exp, true, #exp, __FILE__, __LINE__, __PRETTY_FUNCTION__) + #define TEST_EQ_FUNC(exp, val) TestEq(exp, val, #exp, __FILE__, __LINE__, __PRETTY_FUNCTION__) +#endif +#define TEST_NOTNULL(exp) TestEq(exp == NULL, false, #exp, __FILE__, __LINE__) +#define TEST_EQ_STR(exp, val) TestEqStr(exp, val, #exp, __FILE__, __LINE__) + +#endif // TEST_ASSERT_H diff --git a/tests/test_builder.cpp b/tests/test_builder.cpp new file mode 100644 index 000000000..51b30c610 --- /dev/null +++ b/tests/test_builder.cpp @@ -0,0 +1,128 @@ +#include "monster_test_generated.h" +#include "test_builder.h" + +using namespace MyGame::Example; + +const std::string m1_name = "Cyberdemon"; +const Color m1_color = Color_Red; +const std::string m2_name = "Imp"; +const Color m2_color = Color_Green; + +struct OwnedAllocator : public flatbuffers::DefaultAllocator {}; + +class TestHeapBuilder : public flatbuffers::FlatBufferBuilder { +private: + TestHeapBuilder(const TestHeapBuilder &); + TestHeapBuilder &operator=(const TestHeapBuilder &); + +public: + TestHeapBuilder() + : flatbuffers::FlatBufferBuilder(2048, new OwnedAllocator(), true) {} + + TestHeapBuilder(TestHeapBuilder &&other) + : FlatBufferBuilder(std::move(other)) { } + + TestHeapBuilder &operator=(TestHeapBuilder &&other) { + FlatBufferBuilder::operator=(std::move(other)); + return *this; + } +}; + +// This class simulates flatbuffers::grpc::detail::SliceAllocatorMember +struct AllocatorMember { + flatbuffers::DefaultAllocator member_allocator_; +}; + +struct GrpcLikeMessageBuilder : private AllocatorMember, + public flatbuffers::FlatBufferBuilder { +private: + GrpcLikeMessageBuilder(const GrpcLikeMessageBuilder &); + GrpcLikeMessageBuilder &operator=(const GrpcLikeMessageBuilder &); + +public: + GrpcLikeMessageBuilder() + : flatbuffers::FlatBufferBuilder(1024, &member_allocator_, false) {} + + GrpcLikeMessageBuilder(GrpcLikeMessageBuilder &&other) + : FlatBufferBuilder(1024, &member_allocator_, false) { + // Default construct and swap idiom. + Swap(other); + } + + GrpcLikeMessageBuilder &operator=(GrpcLikeMessageBuilder &&other) { + // Construct temporary and swap idiom + GrpcLikeMessageBuilder temp(std::move(other)); + Swap(temp); + return *this; + } + + void Swap(GrpcLikeMessageBuilder &other) { + // No need to swap member_allocator_ because it's stateless. + FlatBufferBuilder::Swap(other); + // After swapping the FlatBufferBuilder, we swap back the allocator, which restores + // the original allocator back in place. This is necessary because MessageBuilder's + // allocator is its own member (SliceAllocatorMember). The allocator passed to + // FlatBufferBuilder::vector_downward must point to this member. + buf_.swap_allocator(other.buf_); + } +}; + +flatbuffers::Offset populate1(flatbuffers::FlatBufferBuilder &builder) { + auto name_offset = builder.CreateString(m1_name); + return CreateMonster(builder, nullptr, 0, 0, name_offset, 0, m1_color); +} + +flatbuffers::Offset populate2(flatbuffers::FlatBufferBuilder &builder) { + auto name_offset = builder.CreateString(m2_name); + return CreateMonster(builder, nullptr, 0, 0, name_offset, 0, m2_color); +} + +uint8_t *release_raw_base(flatbuffers::FlatBufferBuilder &fbb, size_t &size, size_t &offset) { + return fbb.ReleaseRaw(size, offset); +} + +void free_raw(flatbuffers::grpc::MessageBuilder &, uint8_t *) { + // release_raw_base calls FlatBufferBuilder::ReleaseRaw on the argument MessageBuilder. + // It's semantically wrong as MessageBuilder has its own ReleaseRaw member function that + // takes three arguments. In such cases though, ~MessageBuilder() invokes + // ~SliceAllocator() that takes care of deleting memory as it calls grpc_slice_unref. + // Obviously, this behavior is very surprising as the pointer returned by + // FlatBufferBuilder::ReleaseRaw is not valid as soon as MessageBuilder goes out of scope. + // This problem does not occur with FlatBufferBuilder. +} + +void free_raw(flatbuffers::FlatBufferBuilder &, uint8_t *buf) { + flatbuffers::DefaultAllocator().deallocate(buf, 0); +} + +bool verify(const flatbuffers::DetachedBuffer &buf, const std::string &expected_name, Color color) { + const Monster *monster = flatbuffers::GetRoot(buf.data()); + return (monster->name()->str() == expected_name) && (monster->color() == color); +} + +bool verify(const uint8_t *buf, size_t offset, const std::string &expected_name, Color color) { + const Monster *monster = flatbuffers::GetRoot(buf+offset); + return (monster->name()->str() == expected_name) && (monster->color() == color); +} + +bool release_n_verify(flatbuffers::FlatBufferBuilder &fbb, const std::string &expected_name, Color color) { + flatbuffers::DetachedBuffer buf = fbb.Release(); + return verify(buf, expected_name, color); +} + +void FlatBufferBuilderTest() { + BuilderTests::all_tests(); + BuilderTests::all_tests(); + BuilderTests::all_tests(); + + BuilderReuseTestSelector tests[4] = { + REUSABLE_AFTER_RELEASE, + REUSABLE_AFTER_RELEASE_RAW, + REUSABLE_AFTER_RELEASE_AND_MOVE_ASSIGN, + REUSABLE_AFTER_RELEASE_RAW_AND_MOVE_ASSIGN + }; + + BuilderReuseTests::run_tests(TestSelector(tests, tests+4)); + BuilderReuseTests::run_tests(TestSelector(tests, tests+4)); + BuilderReuseTests::run_tests(TestSelector(tests, tests+4)); +} diff --git a/tests/test_builder.h b/tests/test_builder.h new file mode 100644 index 000000000..8d32890bd --- /dev/null +++ b/tests/test_builder.h @@ -0,0 +1,273 @@ +#ifndef TEST_BUILDER_H +#define TEST_BUILDER_H + +#include +#include "monster_test_generated.h" +#include "flatbuffers/flatbuffers.h" +#include "test_assert.h" + +using namespace MyGame::Example; +namespace flatbuffers { +namespace grpc { +class MessageBuilder; +} +} + +extern const std::string m1_name; +extern const Color m1_color; +extern const std::string m2_name; +extern const Color m2_color; + +flatbuffers::Offset populate1(flatbuffers::FlatBufferBuilder &builder); +flatbuffers::Offset populate2(flatbuffers::FlatBufferBuilder &builder); + +uint8_t *release_raw_base(flatbuffers::FlatBufferBuilder &fbb, size_t &size, size_t &offset); + +void free_raw(flatbuffers::grpc::MessageBuilder &mbb, uint8_t *buf); +void free_raw(flatbuffers::FlatBufferBuilder &fbb, uint8_t *buf); + +bool verify(const flatbuffers::DetachedBuffer &buf, const std::string &expected_name, Color color); +bool verify(const uint8_t *buf, size_t offset, const std::string &expected_name, Color color); + +bool release_n_verify(flatbuffers::FlatBufferBuilder &fbb, const std::string &expected_name, Color color); +bool release_n_verify(flatbuffers::grpc::MessageBuilder &mbb, const std::string &expected_name, Color color); + +template +struct BuilderTests { + static void empty_builder_movector_test() { + Builder b1; + size_t b1_size = b1.GetSize(); + Builder b2(std::move(b1)); + size_t b2_size = b2.GetSize(); + TEST_EQ_FUNC(b1_size, 0); + TEST_EQ_FUNC(b1_size, b2_size); + } + + static void nonempty_builder_movector_test() { + Builder b1; + populate1(b1); + size_t b1_size = b1.GetSize(); + Builder b2(std::move(b1)); + TEST_EQ_FUNC(b1_size, b2.GetSize()); + TEST_EQ_FUNC(b1.GetSize(), 0); + } + + static void builder_movector_before_finish_test() { + Builder b1; + auto root_offset1 = populate1(b1); + Builder b2(std::move(b1)); + b2.Finish(root_offset1); + TEST_ASSERT_FUNC(release_n_verify(b2, m1_name, m1_color)); + TEST_EQ_FUNC(b1.GetSize(), 0); + } + + static void builder_movector_after_finish_test() { + Builder b1; + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + auto b1_size = b1.GetSize(); + Builder b2(std::move(b1)); + TEST_EQ_FUNC(b2.GetSize(), b1_size); + TEST_ASSERT_FUNC(release_n_verify(b2, m1_name, m1_color)); + TEST_EQ_FUNC(b1.GetSize(), 0); + } + + static void builder_move_assign_before_finish_test() { + Builder b1; + auto root_offset1 = populate1(b1); + Builder b2; + populate2(b2); + b2 = std::move(b1); + b2.Finish(root_offset1); + TEST_ASSERT_FUNC(release_n_verify(b2, m1_name, m1_color)); + TEST_EQ_FUNC(b1.GetSize(), 0); + } + + static void builder_move_assign_after_finish_test() { + Builder b1; + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + auto b1_size = b1.GetSize(); + Builder b2; + auto root_offset2 = populate2(b2); + b2.Finish(root_offset2); + b2 = std::move(b1); + TEST_EQ_FUNC(b2.GetSize(), b1_size); + TEST_ASSERT_FUNC(release_n_verify(b2, m1_name, m1_color)); + TEST_EQ_FUNC(b1.GetSize(), 0); + } + + static void builder_swap_before_finish_test() { + Builder b1; + auto root_offset1 = populate1(b1); + auto size1 = b1.GetSize(); + Builder b2; + auto root_offset2 = populate2(b2); + auto size2 = b2.GetSize(); + b1.Swap(b2); + b1.Finish(root_offset2); + b2.Finish(root_offset1); + TEST_EQ_FUNC(b1.GetSize() > size2, true); + TEST_EQ_FUNC(b2.GetSize() > size1, true); + TEST_ASSERT_FUNC(release_n_verify(b1, m2_name, m2_color)); + TEST_ASSERT_FUNC(release_n_verify(b2, m1_name, m1_color)); + } + + static void builder_swap_after_finish_test() { + Builder b1; + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + auto size1 = b1.GetSize(); + Builder b2; + auto root_offset2 = populate2(b2); + b2.Finish(root_offset2); + auto size2 = b2.GetSize(); + b1.Swap(b2); + TEST_EQ_FUNC(b1.GetSize(), size2); + TEST_EQ_FUNC(b2.GetSize(), size1); + TEST_ASSERT_FUNC(release_n_verify(b1, m2_name, m2_color)); + TEST_ASSERT_FUNC(release_n_verify(b2, m1_name, m1_color)); + } + + static void builder_move_assign_after_release_test() { + Builder b1; + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + { + flatbuffers::DetachedBuffer b1_detached = b1.Release(); + // detached buffer is deleted + } + Builder b2; + auto root_offset2 = populate2(b2); + b2.Finish(root_offset2); + auto b2_size = b2.GetSize(); + // Move into a released builder. + b1 = std::move(b2); + TEST_EQ_FUNC(b1.GetSize(), b2_size); + TEST_ASSERT_FUNC(release_n_verify(b1, m2_name, m2_color)); + TEST_EQ_FUNC(b2.GetSize(), 0); + } + + static void builder_move_assign_after_releaseraw_test() { + Builder b1; + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + size_t size, offset; + uint8_t *buf = release_raw_base(b1, size, offset); + TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color)); + free_raw(b1, buf); + Builder b2; + auto root_offset2 = populate2(b2); + b2.Finish(root_offset2); + auto b2_size = b2.GetSize(); + // Move into a released builder. + b1 = std::move(b2); + TEST_EQ_FUNC(b1.GetSize(), b2_size); + TEST_ASSERT_FUNC(release_n_verify(b1, m2_name, m2_color)); + TEST_EQ_FUNC(b2.GetSize(), 0); + } + + static void all_tests() { + empty_builder_movector_test(); + nonempty_builder_movector_test(); + builder_movector_before_finish_test(); + builder_movector_after_finish_test(); + builder_move_assign_before_finish_test(); + builder_move_assign_after_finish_test(); + builder_swap_before_finish_test(); + builder_swap_after_finish_test(); + builder_move_assign_after_release_test(); + builder_move_assign_after_releaseraw_test(); + } +}; + +enum BuilderReuseTestSelector { + REUSABLE_AFTER_RELEASE = 1, + REUSABLE_AFTER_RELEASE_RAW = 2, + REUSABLE_AFTER_RELEASE_MESSAGE = 3, + REUSABLE_AFTER_RELEASE_AND_MOVE_ASSIGN = 4, + REUSABLE_AFTER_RELEASE_RAW_AND_MOVE_ASSIGN = 5, + REUSABLE_AFTER_RELEASE_MESSAGE_AND_MOVE_ASSIGN = 6 +}; + +typedef std::set TestSelector; + +template +struct BuilderReuseTests { + static void builder_reusable_after_release_test(TestSelector selector) { + if (!selector.count(REUSABLE_AFTER_RELEASE)) { + return; + } + + Builder b1; + std::vector buffers; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + buffers.push_back(b1.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; + } + + Builder b1; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + size_t size, offset; + uint8_t *buf = release_raw_base(b1, size, offset); + TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color)); + free_raw(b1, buf); + } + } + + static void builder_reusable_after_release_and_move_assign_test(TestSelector selector) { + if (!selector.count(REUSABLE_AFTER_RELEASE_AND_MOVE_ASSIGN)) { + return; + } + + Builder b1; + std::vector buffers; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + buffers.push_back(b1.Release()); + TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color)); + Builder b2; + b1 = std::move(b2); + TEST_EQ_FUNC(b2.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; + } + + Builder b1; + for (int i = 0; i < 5; ++i) { + auto root_offset1 = populate1(b1); + b1.Finish(root_offset1); + size_t size, offset; + uint8_t *buf = release_raw_base(b1, size, offset); + TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color)); + free_raw(b1, buf); + Builder b2; + b1 = std::move(b2); + TEST_EQ_FUNC(b2.GetSize(), 0); + } + } + + static void run_tests(TestSelector selector) { + builder_reusable_after_release_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); + } +}; + +#endif // TEST_BUILDER_H