Squashed 'third_party/flatbuffers/' content from commit acc9990ab
Change-Id: I48550d40d78fea996ebe74e9723a5d1f910de491
git-subtree-dir: third_party/flatbuffers
git-subtree-split: acc9990abd2206491480291b0f85f925110102ea
diff --git a/grpc/tests/GameFactory.java b/grpc/tests/GameFactory.java
new file mode 100644
index 0000000..520ae39
--- /dev/null
+++ b/grpc/tests/GameFactory.java
@@ -0,0 +1,42 @@
+import java.nio.ByteBuffer;
+import MyGame.Example.Monster;
+import MyGame.Example.Stat;
+import com.google.flatbuffers.FlatBufferBuilder;
+
+class GameFactory {
+ public static Monster createMonster(String monsterName, short nestedMonsterHp, short nestedMonsterMana) {
+ FlatBufferBuilder builder = new FlatBufferBuilder();
+
+ int name_offset = builder.createString(monsterName);
+ Monster.startMonster(builder);
+ Monster.addName(builder, name_offset);
+ Monster.addHp(builder, nestedMonsterHp);
+ Monster.addMana(builder, nestedMonsterMana);
+ int monster_offset = Monster.endMonster(builder);
+ Monster.finishMonsterBuffer(builder, monster_offset);
+
+ ByteBuffer buffer = builder.dataBuffer();
+ Monster monster = Monster.getRootAsMonster(buffer);
+ return monster;
+ }
+
+ public static Monster createMonsterFromStat(Stat stat, int seqNo) {
+ FlatBufferBuilder builder = new FlatBufferBuilder();
+ int name_offset = builder.createString(stat.id() + " No." + seqNo);
+ Monster.startMonster(builder);
+ Monster.addName(builder, name_offset);
+ int monster_offset = Monster.endMonster(builder);
+ Monster.finishMonsterBuffer(builder, monster_offset);
+ Monster monster = Monster.getRootAsMonster(builder.dataBuffer());
+ return monster;
+ }
+
+ public static Stat createStat(String greeting, long val, int count) {
+ FlatBufferBuilder builder = new FlatBufferBuilder();
+ int statOffset = Stat.createStat(builder, builder.createString(greeting), val, count);
+ builder.finish(statOffset);
+ Stat stat = Stat.getRootAsStat(builder.dataBuffer());
+ return stat;
+ }
+
+}
diff --git a/grpc/tests/JavaGrpcTest.java b/grpc/tests/JavaGrpcTest.java
new file mode 100644
index 0000000..98a67b5
--- /dev/null
+++ b/grpc/tests/JavaGrpcTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MyGame.Example.Monster;
+import MyGame.Example.MonsterStorageGrpc;
+import MyGame.Example.Stat;
+import com.google.flatbuffers.FlatBufferBuilder;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.stub.StreamObserver;
+import org.junit.Assert;
+
+import java.io.IOException;
+import java.lang.InterruptedException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.CountDownLatch;
+
+
+/**
+ * Demonstrates basic client-server interaction using grpc-java over netty.
+ */
+public class JavaGrpcTest {
+ static final String BIG_MONSTER_NAME = "Cyberdemon";
+ static final short nestedMonsterHp = 600;
+ static final short nestedMonsterMana = 1024;
+ static final int numStreamedMsgs = 10;
+ static final int timeoutMs = 3000;
+ static Server server;
+ static ManagedChannel channel;
+ static MonsterStorageGrpc.MonsterStorageBlockingStub blockingStub;
+ static MonsterStorageGrpc.MonsterStorageStub asyncStub;
+
+ static class MyService extends MonsterStorageGrpc.MonsterStorageImplBase {
+ @Override
+ public void store(Monster request, io.grpc.stub.StreamObserver<Stat> responseObserver) {
+ Assert.assertEquals(request.name(), BIG_MONSTER_NAME);
+ Assert.assertEquals(request.hp(), nestedMonsterHp);
+ Assert.assertEquals(request.mana(), nestedMonsterMana);
+ System.out.println("Received store request from " + request.name());
+ // Create a response from the incoming request name.
+ Stat stat = GameFactory.createStat("Hello " + request.name(), 100, 10);
+ responseObserver.onNext(stat);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void retrieve(Stat request, io.grpc.stub.StreamObserver<Monster> responseObserver) {
+ // Create 10 monsters for streaming response.
+ for (int i=0; i<numStreamedMsgs; i++) {
+ Monster monster = GameFactory.createMonsterFromStat(request, i);
+ responseObserver.onNext(monster);
+ }
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver<Monster> getMaxHitPoint(final StreamObserver<Stat> responseObserver) {
+ return computeMinMax(responseObserver, false);
+ }
+
+ @Override
+ public StreamObserver<Monster> getMinMaxHitPoints(final StreamObserver<Stat> responseObserver) {
+ return computeMinMax(responseObserver, true);
+ }
+
+ private StreamObserver<Monster> computeMinMax(final StreamObserver<Stat> responseObserver, final boolean includeMin) {
+ final AtomicInteger maxHp = new AtomicInteger(Integer.MIN_VALUE);
+ final AtomicReference<String> maxHpMonsterName = new AtomicReference<String>();
+ final AtomicInteger maxHpCount = new AtomicInteger();
+
+ final AtomicInteger minHp = new AtomicInteger(Integer.MAX_VALUE);
+ final AtomicReference<String> minHpMonsterName = new AtomicReference<String>();
+ final AtomicInteger minHpCount = new AtomicInteger();
+
+ return new StreamObserver<Monster>() {
+ public void onNext(Monster monster) {
+ if (monster.hp() > maxHp.get()) {
+ // Found a monster of higher hit points.
+ maxHp.set(monster.hp());
+ maxHpMonsterName.set(monster.name());
+ maxHpCount.set(1);
+ }
+ else if (monster.hp() == maxHp.get()) {
+ // Count how many times we saw a monster of current max hit points.
+ maxHpCount.getAndIncrement();
+ }
+
+ if (monster.hp() < minHp.get()) {
+ // Found a monster of a lower hit points.
+ minHp.set(monster.hp());
+ minHpMonsterName.set(monster.name());
+ minHpCount.set(1);
+ }
+ else if (monster.hp() == minHp.get()) {
+ // Count how many times we saw a monster of current min hit points.
+ minHpCount.getAndIncrement();
+ }
+ }
+ public void onCompleted() {
+ Stat maxHpStat = GameFactory.createStat(maxHpMonsterName.get(), maxHp.get(), maxHpCount.get());
+ // Send max hit points first.
+ responseObserver.onNext(maxHpStat);
+ if (includeMin) {
+ // Send min hit points.
+ Stat minHpStat = GameFactory.createStat(minHpMonsterName.get(), minHp.get(), minHpCount.get());
+ responseObserver.onNext(minHpStat);
+ }
+ responseObserver.onCompleted();
+ }
+ public void onError(Throwable t) {
+ // Not expected
+ Assert.fail();
+ };
+ };
+ }
+ }
+
+ @org.junit.BeforeClass
+ public static void startServer() throws IOException {
+ server = ServerBuilder.forPort(0).addService(new MyService()).build().start();
+ int port = server.getPort();
+ channel = ManagedChannelBuilder.forAddress("localhost", port)
+ // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
+ // needing certificates.
+ .usePlaintext(true)
+ .directExecutor()
+ .build();
+ blockingStub = MonsterStorageGrpc.newBlockingStub(channel);
+ asyncStub = MonsterStorageGrpc.newStub(channel);
+ }
+
+ @org.junit.Test
+ public void testUnary() throws IOException {
+ Monster monsterRequest = GameFactory.createMonster(BIG_MONSTER_NAME, nestedMonsterHp, nestedMonsterMana);
+ Stat stat = blockingStub.store(monsterRequest);
+ Assert.assertEquals(stat.id(), "Hello " + BIG_MONSTER_NAME);
+ System.out.println("Received stat response from service: " + stat.id());
+ }
+
+ @org.junit.Test
+ public void testServerStreaming() throws IOException {
+ Monster monsterRequest = GameFactory.createMonster(BIG_MONSTER_NAME, nestedMonsterHp, nestedMonsterMana);
+ Stat stat = blockingStub.store(monsterRequest);
+ Iterator<Monster> iterator = blockingStub.retrieve(stat);
+ int counter = 0;
+ while(iterator.hasNext()) {
+ Monster m = iterator.next();
+ System.out.println("Received monster " + m.name());
+ counter ++;
+ }
+ Assert.assertEquals(counter, numStreamedMsgs);
+ System.out.println("FlatBuffers GRPC client/server test: completed successfully");
+ }
+
+ @org.junit.Test
+ public void testClientStreaming() throws IOException, InterruptedException {
+ final AtomicReference<Stat> maxHitStat = new AtomicReference<Stat>();
+ final CountDownLatch streamAlive = new CountDownLatch(1);
+
+ StreamObserver<Stat> statObserver = new StreamObserver<Stat>() {
+ public void onCompleted() {
+ streamAlive.countDown();
+ }
+ public void onError(Throwable ex) { }
+ public void onNext(Stat stat) {
+ maxHitStat.set(stat);
+ }
+ };
+ StreamObserver<Monster> monsterStream = asyncStub.getMaxHitPoint(statObserver);
+ short count = 10;
+ for (short i = 0;i < count; ++i) {
+ Monster monster = GameFactory.createMonster(BIG_MONSTER_NAME + i, (short) (nestedMonsterHp * i), nestedMonsterMana);
+ monsterStream.onNext(monster);
+ }
+ monsterStream.onCompleted();
+ // Wait a little bit for the server to send the stats of the monster with the max hit-points.
+ streamAlive.await(timeoutMs, TimeUnit.MILLISECONDS);
+ Assert.assertEquals(maxHitStat.get().id(), BIG_MONSTER_NAME + (count - 1));
+ Assert.assertEquals(maxHitStat.get().val(), nestedMonsterHp * (count - 1));
+ Assert.assertEquals(maxHitStat.get().count(), 1);
+ }
+
+ @org.junit.Test
+ public void testBiDiStreaming() throws IOException, InterruptedException {
+ final AtomicReference<Stat> maxHitStat = new AtomicReference<Stat>();
+ final AtomicReference<Stat> minHitStat = new AtomicReference<Stat>();
+ final CountDownLatch streamAlive = new CountDownLatch(1);
+
+ StreamObserver<Stat> statObserver = new StreamObserver<Stat>() {
+ public void onCompleted() {
+ streamAlive.countDown();
+ }
+ public void onError(Throwable ex) { }
+ public void onNext(Stat stat) {
+ // We expect the server to send the max stat first and then the min stat.
+ if (maxHitStat.get() == null) {
+ maxHitStat.set(stat);
+ }
+ else {
+ minHitStat.set(stat);
+ }
+ }
+ };
+ StreamObserver<Monster> monsterStream = asyncStub.getMinMaxHitPoints(statObserver);
+ short count = 10;
+ for (short i = 0;i < count; ++i) {
+ Monster monster = GameFactory.createMonster(BIG_MONSTER_NAME + i, (short) (nestedMonsterHp * i), nestedMonsterMana);
+ monsterStream.onNext(monster);
+ }
+ monsterStream.onCompleted();
+
+ // Wait a little bit for the server to send the stats of the monster with the max and min hit-points.
+ streamAlive.await(timeoutMs, TimeUnit.MILLISECONDS);
+
+ Assert.assertEquals(maxHitStat.get().id(), BIG_MONSTER_NAME + (count - 1));
+ Assert.assertEquals(maxHitStat.get().val(), nestedMonsterHp * (count - 1));
+ Assert.assertEquals(maxHitStat.get().count(), 1);
+
+ Assert.assertEquals(minHitStat.get().id(), BIG_MONSTER_NAME + 0);
+ Assert.assertEquals(minHitStat.get().val(), nestedMonsterHp * 0);
+ Assert.assertEquals(minHitStat.get().count(), 1);
+ }
+}
diff --git a/grpc/tests/go_test.go b/grpc/tests/go_test.go
new file mode 100644
index 0000000..288036b
--- /dev/null
+++ b/grpc/tests/go_test.go
@@ -0,0 +1,93 @@
+package testing
+
+import (
+ "../../tests/MyGame/Example"
+
+ "context"
+ "net"
+ "testing"
+
+ "google.golang.org/grpc"
+)
+
+type server struct{}
+
+// test used to send and receive in grpc methods
+var test = "Flatbuffers"
+var addr = "0.0.0.0:50051"
+
+// gRPC server store method
+func (s *server) Store(context context.Context, in *Example.Monster) (*flatbuffers.Builder, error) {
+ b := flatbuffers.NewBuilder(0)
+ i := b.CreateString(test)
+ Example.StatStart(b)
+ Example.StatAddId(b, i)
+ b.Finish(Example.StatEnd(b))
+ return b, nil
+
+}
+
+// gRPC server retrieve method
+func (s *server) Retrieve(context context.Context, in *Example.Stat) (*flatbuffers.Builder, error) {
+ b := flatbuffers.NewBuilder(0)
+ i := b.CreateString(test)
+ Example.MonsterStart(b)
+ Example.MonsterAddName(b, i)
+ b.Finish(Example.MonsterEnd(b))
+ return b, nil
+}
+
+func StoreClient(c Example.MonsterStorageClient, t *testing.T) {
+ b := flatbuffers.NewBuilder(0)
+ i := b.CreateString(test)
+ Example.MonsterStart(b)
+ Example.MonsterAddName(b, i)
+ b.Finish(Example.MonsterEnd(b))
+ out, err := c.Store(context.Background(), b)
+ if err != nil {
+ t.Fatalf("Store client failed: %v", err)
+ }
+ if string(out.Id()) != test {
+ t.Errorf("StoreClient failed: expected=%s, got=%s\n", test, out.Id())
+ t.Fail()
+ }
+}
+
+func RetrieveClient(c Example.MonsterStorageClient, t *testing.T) {
+ b := flatbuffers.NewBuilder(0)
+ i := b.CreateString(test)
+ Example.StatStart(b)
+ Example.StatAddId(b, i)
+ b.Finish(Example.StatEnd(b))
+ out, err := c.Retrieve(context.Background(), b)
+ if err != nil {
+ t.Fatalf("Retrieve client failed: %v", err)
+ }
+ if string(out.Name()) != test {
+ t.Errorf("RetrieveClient failed: expected=%s, got=%s\n", test, out.Name())
+ t.Fail()
+ }
+}
+
+func TestGRPC(t *testing.T) {
+ lis, err := net.Listen("tcp", addr)
+ if err != nil {
+ t.Fatalf("Failed to listen: %v", err)
+ }
+ ser := grpc.NewServer(grpc.CustomCodec(flatbuffers.FlatbuffersCodec{}))
+ Example.RegisterMonsterStorageServer(ser, &server{})
+ go func() {
+ if err := ser.Serve(lis); err != nil {
+ t.Fatalf("Failed to serve: %v", err)
+ t.FailNow()
+ }
+ }()
+ conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithCodec(flatbuffers.FlatbuffersCodec{}))
+ if err != nil {
+ t.Fatalf("Failed to connect: %v", err)
+ }
+ defer conn.Close()
+ client := Example.NewMonsterStorageClient(conn)
+ StoreClient(client, t)
+ RetrieveClient(client, t)
+}
diff --git a/grpc/tests/grpctest.cpp b/grpc/tests/grpctest.cpp
new file mode 100644
index 0000000..7e5c6e6
--- /dev/null
+++ b/grpc/tests/grpctest.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <thread>
+
+#include <grpc++/grpc++.h>
+
+#include "monster_test.grpc.fb.h"
+#include "monster_test_generated.h"
+#include "test_assert.h"
+
+using namespace MyGame::Example;
+using flatbuffers::grpc::MessageBuilder;
+using flatbuffers::FlatBufferBuilder;
+
+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.
+class ServiceImpl final : public MyGame::Example::MonsterStorage::Service {
+ virtual ::grpc::Status Store(
+ ::grpc::ServerContext *context,
+ const flatbuffers::grpc::Message<Monster> *request,
+ flatbuffers::grpc::Message<Stat> *response) override {
+ // Create a response from the incoming request name.
+ fbb_.Clear();
+ auto stat_offset = CreateStat(
+ fbb_, fbb_.CreateString("Hello, " + request->GetRoot()->name()->str()));
+ fbb_.Finish(stat_offset);
+ // Transfer ownership of the message to gRPC
+ *response = fbb_.ReleaseMessage<Stat>();
+ return grpc::Status::OK;
+ }
+ virtual ::grpc::Status Retrieve(
+ ::grpc::ServerContext *context,
+ const flatbuffers::grpc::Message<Stat> *request,
+ ::grpc::ServerWriter<flatbuffers::grpc::Message<Monster>> *writer)
+ override {
+ for (int i = 0; i < 5; i++) {
+ fbb_.Clear();
+ // Create 5 monsters for resposne.
+ auto monster_offset =
+ CreateMonster(fbb_, 0, 0, 0,
+ fbb_.CreateString(request->GetRoot()->id()->str() +
+ " No." + std::to_string(i)));
+ fbb_.Finish(monster_offset);
+
+ flatbuffers::grpc::Message<Monster> monster =
+ fbb_.ReleaseMessage<Monster>();
+
+ // Send monster to client using streaming.
+ writer->Write(monster);
+ }
+ return grpc::Status::OK;
+ }
+
+ private:
+ flatbuffers::grpc::MessageBuilder fbb_;
+};
+
+// Track the server instance, so we can terminate it later.
+grpc::Server *server_instance = nullptr;
+// Mutex to protec this variable.
+std::mutex wait_for_server;
+std::condition_variable server_instance_cv;
+
+// This function implements the server thread.
+void RunServer() {
+ auto server_address = "0.0.0.0:50051";
+ // Callback interface we implemented above.
+ ServiceImpl service;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+
+ // Start the server. Lock to change the variable we're changing.
+ wait_for_server.lock();
+ server_instance = builder.BuildAndStart().release();
+ wait_for_server.unlock();
+ server_instance_cv.notify_one();
+
+ std::cout << "Server listening on " << server_address << std::endl;
+ // This will block the thread and serve requests.
+ server_instance->Wait();
+}
+
+template <class Builder>
+void StoreRPC(MonsterStorage::Stub *stub) {
+ Builder fbb;
+ grpc::ClientContext context;
+ // Build a request with the name set.
+ auto monster_offset = CreateMonster(fbb, 0, 0, 0, fbb.CreateString("Fred"));
+ MessageBuilder mb(std::move(fbb));
+ mb.Finish(monster_offset);
+ auto request = mb.ReleaseMessage<Monster>();
+ flatbuffers::grpc::Message<Stat> response;
+
+ // The actual RPC.
+ auto status = stub->Store(&context, request, &response);
+
+ if (status.ok()) {
+ auto resp = response.GetRoot()->id();
+ std::cout << "RPC response: " << resp->str() << std::endl;
+ } else {
+ std::cout << "RPC failed" << std::endl;
+ }
+}
+
+template <class Builder>
+void RetrieveRPC(MonsterStorage::Stub *stub) {
+ Builder fbb;
+ grpc::ClientContext context;
+ fbb.Clear();
+ auto stat_offset = CreateStat(fbb, fbb.CreateString("Fred"));
+ fbb.Finish(stat_offset);
+ auto request = MessageBuilder(std::move(fbb)).ReleaseMessage<Stat>();
+
+ flatbuffers::grpc::Message<Monster> response;
+ auto stream = stub->Retrieve(&context, request);
+ while (stream->Read(&response)) {
+ auto resp = response.GetRoot()->name();
+ std::cout << "RPC Streaming response: " << resp->str() << std::endl;
+ }
+}
+
+int grpc_server_test() {
+ // Launch server.
+ std::thread server_thread(RunServer);
+
+ // wait for server to spin up.
+ std::unique_lock<std::mutex> lock(wait_for_server);
+ while (!server_instance) server_instance_cv.wait(lock);
+
+ // Now connect the client.
+ auto channel = grpc::CreateChannel("localhost:50051",
+ grpc::InsecureChannelCredentials());
+ auto stub = MyGame::Example::MonsterStorage::NewStub(channel);
+
+ StoreRPC<MessageBuilder>(stub.get());
+ StoreRPC<FlatBufferBuilder>(stub.get());
+
+ RetrieveRPC<MessageBuilder>(stub.get());
+ RetrieveRPC<FlatBufferBuilder>(stub.get());
+
+
+#if !FLATBUFFERS_GRPC_DISABLE_AUTO_VERIFICATION
+ {
+ // Test that an invalid request errors out correctly
+ grpc::ClientContext context;
+ flatbuffers::grpc::Message<Monster> request; // simulate invalid message
+ flatbuffers::grpc::Message<Stat> response;
+ auto status = stub->Store(&context, request, &response);
+ // The rpc status should be INTERNAL to indicate a verification error. This
+ // matches the protobuf gRPC status code for an unparseable message.
+ assert(!status.ok());
+ assert(status.error_code() == ::grpc::StatusCode::INTERNAL);
+ assert(strcmp(status.error_message().c_str(),
+ "Message verification failed") == 0);
+ }
+#endif
+
+ server_instance->Shutdown();
+
+ server_thread.join();
+
+ delete server_instance;
+
+ return 0;
+}
+
+int main(int /*argc*/, const char * /*argv*/ []) {
+ 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/java-grpc-test.sh b/grpc/tests/java-grpc-test.sh
new file mode 100755
index 0000000..ec42960
--- /dev/null
+++ b/grpc/tests/java-grpc-test.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# NOTE: make sure `mvn install` in /gprc is executed before running this test
+mvn test
diff --git a/grpc/tests/message_builder_test.cpp b/grpc/tests/message_builder_test.cpp
new file mode 100644
index 0000000..36f5bc2
--- /dev/null
+++ b/grpc/tests/message_builder_test.cpp
@@ -0,0 +1,340 @@
+#include "flatbuffers/grpc.h"
+#include "monster_test_generated.h"
+#include "test_assert.h"
+#include "test_builder.h"
+
+using MyGame::Example::Vec3;
+using MyGame::Example::CreateStat;
+using MyGame::Example::Any_NONE;
+
+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);
+}
+
+bool release_n_verify(flatbuffers::grpc::MessageBuilder &mbb, const std::string &expected_name, Color color) {
+ flatbuffers::grpc::Message<Monster> msg = mbb.ReleaseMessage<Monster>();
+ const Monster *monster = msg.GetRoot();
+ return (monster->name()->str() == expected_name) && (monster->color() == color);
+}
+
+void builder_move_assign_after_releaseraw_test(flatbuffers::grpc::MessageBuilder dst) {
+ auto root_offset1 = populate1(dst);
+ dst.Finish(root_offset1);
+ size_t size, offset;
+ grpc_slice slice;
+ dst.ReleaseRaw(size, offset, slice);
+ flatbuffers::FlatBufferBuilder src;
+ auto root_offset2 = populate2(src);
+ src.Finish(root_offset2);
+ auto src_size = src.GetSize();
+ // Move into a released builder.
+ dst = std::move(src);
+ TEST_EQ(dst.GetSize(), src_size);
+ TEST_ASSERT(release_n_verify(dst, m2_name, m2_color));
+ TEST_EQ(src.GetSize(), 0);
+ grpc_slice_unref(slice);
+}
+
+template <class SrcBuilder>
+struct BuilderReuseTests<flatbuffers::grpc::MessageBuilder, SrcBuilder> {
+ static void builder_reusable_after_release_message_test(TestSelector selector) {
+ if (!selector.count(REUSABLE_AFTER_RELEASE_MESSAGE)) {
+ return;
+ }
+
+ flatbuffers::grpc::MessageBuilder mb;
+ std::vector<flatbuffers::grpc::Message<Monster>> buffers;
+ for (int i = 0; i < 5; ++i) {
+ auto root_offset1 = populate1(mb);
+ mb.Finish(root_offset1);
+ buffers.push_back(mb.ReleaseMessage<Monster>());
+ TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color));
+ }
+ }
+
+ static void builder_reusable_after_release_test(TestSelector selector) {
+ if (!selector.count(REUSABLE_AFTER_RELEASE)) {
+ return;
+ }
+
+ // FIXME: Populate-Release loop fails assert(GRPC_SLICE_IS_EMPTY(slice_)) in SliceAllocator::allocate
+ // in the second iteration.
+
+ flatbuffers::grpc::MessageBuilder mb;
+ std::vector<flatbuffers::DetachedBuffer> buffers;
+ for (int i = 0; i < 2; ++i) {
+ auto root_offset1 = populate1(mb);
+ mb.Finish(root_offset1);
+ buffers.push_back(mb.Release());
+ TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color));
+ }
+ }
+
+ static void builder_reusable_after_releaseraw_test(TestSelector selector) {
+ if (!selector.count(REUSABLE_AFTER_RELEASE_RAW)) {
+ return;
+ }
+
+ flatbuffers::grpc::MessageBuilder mb;
+ for (int i = 0; i < 5; ++i) {
+ auto root_offset1 = populate1(mb);
+ mb.Finish(root_offset1);
+ size_t size, offset;
+ grpc_slice slice;
+ const uint8_t *buf = mb.ReleaseRaw(size, offset, slice);
+ TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color));
+ grpc_slice_unref(slice);
+ }
+ }
+
+ static void builder_reusable_after_release_and_move_assign_test(TestSelector selector) {
+ if (!selector.count(REUSABLE_AFTER_RELEASE_AND_MOVE_ASSIGN)) {
+ return;
+ }
+
+ // FIXME: Release-move_assign loop fails assert(p == GRPC_SLICE_START_PTR(slice_))
+ // in DetachedBuffer destructor after all the iterations
+
+ flatbuffers::grpc::MessageBuilder dst;
+ std::vector<flatbuffers::DetachedBuffer> buffers;
+
+ for (int i = 0; i < 2; ++i) {
+ auto root_offset1 = populate1(dst);
+ dst.Finish(root_offset1);
+ buffers.push_back(dst.Release());
+ TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color));
+
+ // bring dst back to life.
+ SrcBuilder src;
+ dst = std::move(src);
+ TEST_EQ_FUNC(dst.GetSize(), 0);
+ TEST_EQ_FUNC(src.GetSize(), 0);
+ }
+ }
+
+ static void builder_reusable_after_release_message_and_move_assign_test(TestSelector selector) {
+ if (!selector.count(REUSABLE_AFTER_RELEASE_MESSAGE_AND_MOVE_ASSIGN)) {
+ return;
+ }
+
+ flatbuffers::grpc::MessageBuilder dst;
+ std::vector<flatbuffers::grpc::Message<Monster>> buffers;
+
+ for (int i = 0; i < 5; ++i) {
+ auto root_offset1 = populate1(dst);
+ dst.Finish(root_offset1);
+ buffers.push_back(dst.ReleaseMessage<Monster>());
+ TEST_ASSERT_FUNC(verify(buffers[i], m1_name, m1_color));
+
+ // bring dst back to life.
+ SrcBuilder src;
+ dst = std::move(src);
+ TEST_EQ_FUNC(dst.GetSize(), 0);
+ TEST_EQ_FUNC(src.GetSize(), 0);
+ }
+ }
+
+ static void builder_reusable_after_releaseraw_and_move_assign_test(TestSelector selector) {
+ if (!selector.count(REUSABLE_AFTER_RELEASE_RAW_AND_MOVE_ASSIGN)) {
+ return;
+ }
+
+ flatbuffers::grpc::MessageBuilder dst;
+ for (int i = 0; i < 5; ++i) {
+ auto root_offset1 = populate1(dst);
+ dst.Finish(root_offset1);
+ size_t size, offset;
+ grpc_slice slice = grpc_empty_slice();
+ const uint8_t *buf = dst.ReleaseRaw(size, offset, slice);
+ TEST_ASSERT_FUNC(verify(buf, offset, m1_name, m1_color));
+ grpc_slice_unref(slice);
+
+ SrcBuilder src;
+ dst = std::move(src);
+ TEST_EQ_FUNC(dst.GetSize(), 0);
+ TEST_EQ_FUNC(src.GetSize(), 0);
+ }
+ }
+
+ static void run_tests(TestSelector selector) {
+ builder_reusable_after_release_test(selector);
+ builder_reusable_after_release_message_test(selector);
+ builder_reusable_after_releaseraw_test(selector);
+ builder_reusable_after_release_and_move_assign_test(selector);
+ builder_reusable_after_releaseraw_and_move_assign_test(selector);
+ builder_reusable_after_release_message_and_move_assign_test(selector);
+ }
+};
+
+void slice_allocator_tests() {
+ // move-construct no-delete test
+ {
+ size_t size = 2048;
+ flatbuffers::grpc::SliceAllocator sa1;
+ uint8_t *buf = sa1.allocate(size);
+ TEST_ASSERT_FUNC(buf != 0);
+ buf[0] = 100;
+ buf[size-1] = 200;
+ flatbuffers::grpc::SliceAllocator sa2(std::move(sa1));
+ // buf should not be deleted after move-construct
+ TEST_EQ_FUNC(buf[0], 100);
+ TEST_EQ_FUNC(buf[size-1], 200);
+ // buf is freed here
+ }
+
+ // move-assign test
+ {
+ flatbuffers::grpc::SliceAllocator sa1, sa2;
+ uint8_t *buf = sa1.allocate(2048);
+ sa1 = std::move(sa2);
+ // sa1 deletes previously allocated memory in move-assign.
+ // So buf is no longer usable here.
+ TEST_ASSERT_FUNC(buf != 0);
+ }
+}
+
+/// This function does not populate exactly the first half of the table. But it could.
+void populate_first_half(MyGame::Example::MonsterBuilder &wrapper, flatbuffers::Offset<flatbuffers::String> name_offset) {
+ wrapper.add_name(name_offset);
+ wrapper.add_color(m1_color);
+}
+
+/// This function does not populate exactly the second half of the table. But it could.
+void populate_second_half(MyGame::Example::MonsterBuilder &wrapper) {
+ wrapper.add_hp(77);
+ wrapper.add_mana(88);
+ Vec3 vec3;
+ wrapper.add_pos(&vec3);
+}
+
+/// This function is a hack to update the FlatBufferBuilder reference (fbb_) in the MonsterBuilder object.
+/// This function will break if fbb_ is not the first member in MonsterBuilder. In that case, some offset must be added.
+/// This function is used exclusively for testing correctness of move operations between FlatBufferBuilders.
+/// If MonsterBuilder had a fbb_ pointer, this hack would be unnecessary. That involves a code-generator change though.
+void test_only_hack_update_fbb_reference(MyGame::Example::MonsterBuilder &monsterBuilder,
+ flatbuffers::grpc::MessageBuilder &mb) {
+ *reinterpret_cast<flatbuffers::FlatBufferBuilder **>(&monsterBuilder) = &mb;
+}
+
+/// This test validates correctness of move conversion of FlatBufferBuilder to a MessageBuilder DURING
+/// a table construction. Half of the table is constructed using FlatBufferBuilder and the other half
+/// of the table is constructed using a MessageBuilder.
+void builder_move_ctor_conversion_before_finish_half_n_half_table_test() {
+ for (size_t initial_size = 4 ; initial_size <= 2048; initial_size *= 2) {
+ flatbuffers::FlatBufferBuilder fbb(initial_size);
+ auto name_offset = fbb.CreateString(m1_name);
+ MyGame::Example::MonsterBuilder monsterBuilder(fbb); // starts a table in FlatBufferBuilder
+ populate_first_half(monsterBuilder, name_offset);
+ flatbuffers::grpc::MessageBuilder mb(std::move(fbb));
+ test_only_hack_update_fbb_reference(monsterBuilder, mb); // hack
+ populate_second_half(monsterBuilder);
+ mb.Finish(monsterBuilder.Finish()); // ends the table in MessageBuilder
+ TEST_ASSERT_FUNC(release_n_verify(mb, m1_name, m1_color));
+ TEST_EQ_FUNC(fbb.GetSize(), 0);
+ }
+}
+
+/// This test populates a COMPLETE inner table before move conversion and later populates more members in the outer table.
+void builder_move_ctor_conversion_before_finish_test() {
+ for (size_t initial_size = 4 ; initial_size <= 2048; initial_size *= 2) {
+ flatbuffers::FlatBufferBuilder fbb(initial_size);
+ auto stat_offset = CreateStat(fbb, fbb.CreateString("SomeId"), 0, 0);
+ flatbuffers::grpc::MessageBuilder mb(std::move(fbb));
+ auto monster_offset = CreateMonster(mb, 0, 150, 100, mb.CreateString(m1_name), 0, m1_color, Any_NONE, 0, 0, 0, 0, 0, 0, stat_offset);
+ mb.Finish(monster_offset);
+ TEST_ASSERT_FUNC(release_n_verify(mb, m1_name, m1_color));
+ TEST_EQ_FUNC(fbb.GetSize(), 0);
+ }
+}
+
+/// This test validates correctness of move conversion of FlatBufferBuilder to a MessageBuilder DURING
+/// a table construction. Half of the table is constructed using FlatBufferBuilder and the other half
+/// of the table is constructed using a MessageBuilder.
+void builder_move_assign_conversion_before_finish_half_n_half_table_test() {
+ flatbuffers::FlatBufferBuilder fbb;
+ flatbuffers::grpc::MessageBuilder mb;
+
+ for (int i = 0;i < 5; ++i) {
+ flatbuffers::FlatBufferBuilder fbb;
+ auto name_offset = fbb.CreateString(m1_name);
+ MyGame::Example::MonsterBuilder monsterBuilder(fbb); // starts a table in FlatBufferBuilder
+ populate_first_half(monsterBuilder, name_offset);
+ mb = std::move(fbb);
+ test_only_hack_update_fbb_reference(monsterBuilder, mb); // hack
+ populate_second_half(monsterBuilder);
+ mb.Finish(monsterBuilder.Finish()); // ends the table in MessageBuilder
+ TEST_ASSERT_FUNC(release_n_verify(mb, m1_name, m1_color));
+ TEST_EQ_FUNC(fbb.GetSize(), 0);
+ }
+}
+
+/// This test populates a COMPLETE inner table before move conversion and later populates more members in the outer table.
+void builder_move_assign_conversion_before_finish_test() {
+ flatbuffers::FlatBufferBuilder fbb;
+ flatbuffers::grpc::MessageBuilder mb;
+
+ for (int i = 0;i < 5; ++i) {
+ auto stat_offset = CreateStat(fbb, fbb.CreateString("SomeId"), 0, 0);
+ mb = std::move(fbb);
+ auto monster_offset = CreateMonster(mb, 0, 150, 100, mb.CreateString(m1_name), 0, m1_color, Any_NONE, 0, 0, 0, 0, 0, 0, stat_offset);
+ mb.Finish(monster_offset);
+ TEST_ASSERT_FUNC(release_n_verify(mb, m1_name, m1_color));
+ TEST_EQ_FUNC(fbb.GetSize(), 0);
+ }
+}
+
+/// This test populates data, finishes the buffer, and does move conversion after.
+void builder_move_ctor_conversion_after_finish_test() {
+ flatbuffers::FlatBufferBuilder fbb;
+ fbb.Finish(populate1(fbb));
+ flatbuffers::grpc::MessageBuilder mb(std::move(fbb));
+ TEST_ASSERT_FUNC(release_n_verify(mb, m1_name, m1_color));
+ TEST_EQ_FUNC(fbb.GetSize(), 0);
+}
+
+/// This test populates data, finishes the buffer, and does move conversion after.
+void builder_move_assign_conversion_after_finish_test() {
+ flatbuffers::FlatBufferBuilder fbb;
+ flatbuffers::grpc::MessageBuilder mb;
+
+ for (int i = 0;i < 5; ++i) {
+ fbb.Finish(populate1(fbb));
+ mb = std::move(fbb);
+ TEST_ASSERT_FUNC(release_n_verify(mb, m1_name, m1_color));
+ TEST_EQ_FUNC(fbb.GetSize(), 0);
+ }
+}
+
+void message_builder_tests() {
+ using flatbuffers::grpc::MessageBuilder;
+ using flatbuffers::FlatBufferBuilder;
+
+ slice_allocator_tests();
+
+#ifndef __APPLE__
+ builder_move_ctor_conversion_before_finish_half_n_half_table_test();
+ builder_move_assign_conversion_before_finish_half_n_half_table_test();
+#endif // __APPLE__
+ builder_move_ctor_conversion_before_finish_test();
+ builder_move_assign_conversion_before_finish_test();
+
+ builder_move_ctor_conversion_after_finish_test();
+ builder_move_assign_conversion_after_finish_test();
+
+ BuilderTests<MessageBuilder, MessageBuilder>::all_tests();
+ BuilderTests<MessageBuilder, FlatBufferBuilder>::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<MessageBuilder, MessageBuilder>::run_tests(TestSelector(tests, tests+6));
+ BuilderReuseTests<MessageBuilder, FlatBufferBuilder>::run_tests(TestSelector(tests, tests+6));
+}
diff --git a/grpc/tests/pom.xml b/grpc/tests/pom.xml
new file mode 100644
index 0000000..addc9fa
--- /dev/null
+++ b/grpc/tests/pom.xml
@@ -0,0 +1,73 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.flatbuffers</groupId>
+ <artifactId>flatbuffers-parent</artifactId>
+ <version>1.11.1</version>
+ </parent>
+ <artifactId>grpc-test</artifactId>
+ <description>Example/Test project demonstrating usage of flatbuffers with GRPC-Java instead of protobufs
+ </description>
+ <properties>
+ <gRPC.version>1.11.1</gRPC.version>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.flatbuffers</groupId>
+ <artifactId>flatbuffers-java</artifactId>
+ <version>${project.parent.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.flatbuffers</groupId>
+ <artifactId>flatbuffers-java-grpc</artifactId>
+ <version>${project.parent.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-stub</artifactId>
+ <version>${gRPC.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-netty</artifactId>
+ <version>${gRPC.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-test-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.basedir}</source>
+ <source>${project.basedir}/../../tests</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <!--<testSourceDirectory>${project.basedir}</testSourceDirectory>-->
+ </build>
+</project>
+