Commit 49fed8c4 authored by Sumant Tambe's avatar Sumant Tambe Committed by Wouter van Oortmerssen

Add FlatBufferBuilder move semantics tests to the main test suite (#4902)

* 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
parent b1a925df
......@@ -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",
],
......
......@@ -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
......
......@@ -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
......
......@@ -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;
}
}
#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));
}
......@@ -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;
......
......@@ -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
......
......@@ -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 <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;
......
#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); }
}
#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
#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));
}
#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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment