Add FlatBufferBuilder move semantics tests to the main test suite (#4902)
authorSumant Tambe <sutambe@yahoo.com>
Mon, 24 Sep 2018 19:03:31 +0000 (12:03 -0700)
committerWouter van Oortmerssen <aardappel@gmail.com>
Mon, 24 Sep 2018 19:03:31 +0000 (12:03 -0700)
* Add FlatBufferBuilder move semantics tests to main

Do not eagerly delete/reset allocators in release and release_raw functions
Update android, vs2010 build files
New tests for various types of FlatBufferBuilders and move semantics

* Improve test failure output with function names

12 files changed:
BUILD
CMakeLists.txt
android/jni/Android.mk
grpc/tests/grpctest.cpp
grpc/tests/message_builder_test.cpp
include/flatbuffers/flatbuffers.h
include/flatbuffers/grpc.h
tests/test.cpp
tests/test_assert.cpp [new file with mode: 0644]
tests/test_assert.h [new file with mode: 0644]
tests/test_builder.cpp [new file with mode: 0644]
tests/test_builder.h [new file with mode: 0644]

diff --git a/BUILD b/BUILD
index 1a3ff58..cb78e37 100644 (file)
--- 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",
     ],
index 3d224fe..cb4453f 100644 (file)
@@ -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
index aec561a..b220332 100644 (file)
@@ -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
index cb4326e..50b17cc 100644 (file)
 
 #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;
+  }
 }
 
index 974e050..25f04da 100644 (file)
@@ -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<Monster> populate1(flatbuffers::FlatBufferBuilder &builder) {
-  auto name_offset = builder.CreateString(m1_name);
-  return CreateMonster(builder, nullptr, 0, 0, name_offset, 0, m1_color);
-}
-
-flatbuffers::Offset<Monster> 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<Monster>(buf.data());
+bool verify(flatbuffers::grpc::Message<Monster> &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 {};
-
-struct TestHeapMessageBuilder : public flatbuffers::FlatBufferBuilder {
-  TestHeapMessageBuilder()
-    : flatbuffers::FlatBufferBuilder(2048, new OwnedAllocator(), true) {}
-};
-
-template <class Builder>
-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);
+template <>
+struct BuilderReuseTests<flatbuffers::grpc::MessageBuilder> {
+  static void builder_reusable_after_release_message_test(TestSelector selector) {
+    if (!selector.count(REUSABLE_AFTER_RELEASE_MESSAGE)) {
+      return;
+    }
+
+    flatbuffers::grpc::MessageBuilder b1;
+    std::vector<flatbuffers::grpc::Message<Monster>> buffers;
+    for (int i = 0; i < 5; ++i) {
+      auto root_offset1 = populate1(b1);
+      b1.Finish(root_offset1);
+      buffers.push_back(b1.ReleaseMessage<Monster>());
+      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<flatbuffers::DetachedBuffer> 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<flatbuffers::DetachedBuffer> 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<flatbuffers::grpc::Message<Monster>> buffers;
+
+    for (int i = 0; i < 5; ++i) {
+      auto root_offset1 = populate1(b1);
+      b1.Finish(root_offset1);
+      buffers.push_back(b1.ReleaseMessage<Monster>());
+      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 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);
   }
+};
 
-  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));
+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
   }
 
-  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();
+  // 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);
   }
-};
+}
 
-int builder_tests() {
+void message_builder_tests() {
+  slice_allocator_tests();
   BuilderTests<flatbuffers::grpc::MessageBuilder>::all_tests();
-  BuilderTests<flatbuffers::FlatBufferBuilder>::all_tests();
-  BuilderTests<TestHeapMessageBuilder>::all_tests();
-  return builder_test_error;
+
+  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<flatbuffers::grpc::MessageBuilder>::run_tests(TestSelector(tests, tests+6));
 }
index 386f2cf..573b6a2 100644 (file)
@@ -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<size_t>(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;
index 2bcfa44..00553ef 100644 (file)
@@ -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
index 0aefc5d..4f89dd3 100644 (file)
@@ -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
 
 using namespace MyGame::Example;
 
-#ifdef __ANDROID__
-  #include <android/log.h>
-  #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<typename T, typename U>
-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 (file)
index 0000000..30ab776
--- /dev/null
@@ -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 (file)
index 0000000..5a14466
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef TEST_ASSERT_H
+#define TEST_ASSERT_H
+
+#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/util.h"
+
+#ifdef __ANDROID__
+#include <android/log.h>
+  #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<typename T, typename U>
+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 (file)
index 0000000..51b30c6
--- /dev/null
@@ -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<Monster> populate1(flatbuffers::FlatBufferBuilder &builder) {
+  auto name_offset = builder.CreateString(m1_name);
+  return CreateMonster(builder, nullptr, 0, 0, name_offset, 0, m1_color);
+}
+
+flatbuffers::Offset<Monster> 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<Monster>(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<Monster>(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<flatbuffers::FlatBufferBuilder>::all_tests();
+  BuilderTests<TestHeapBuilder>::all_tests();
+  BuilderTests<GrpcLikeMessageBuilder>::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<flatbuffers::FlatBufferBuilder>::run_tests(TestSelector(tests, tests+4));
+  BuilderReuseTests<TestHeapBuilder>::run_tests(TestSelector(tests, tests+4));
+  BuilderReuseTests<GrpcLikeMessageBuilder>::run_tests(TestSelector(tests, tests+4));
+}
diff --git a/tests/test_builder.h b/tests/test_builder.h
new file mode 100644 (file)
index 0000000..8d32890
--- /dev/null
@@ -0,0 +1,273 @@
+#ifndef TEST_BUILDER_H
+#define TEST_BUILDER_H
+
+#include <set>
+#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<Monster> populate1(flatbuffers::FlatBufferBuilder &builder);
+flatbuffers::Offset<Monster> 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 <class Builder>
+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<BuilderReuseTestSelector> TestSelector;
+
+template <class Builder>
+struct BuilderReuseTests {
+  static void builder_reusable_after_release_test(TestSelector selector) {
+    if (!selector.count(REUSABLE_AFTER_RELEASE)) {
+      return;
+    }
+
+    Builder b1;
+    std::vector<flatbuffers::DetachedBuffer> 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<flatbuffers::DetachedBuffer> 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