package org.deephacks.westty.protobuf; import com.google.common.base.Optional; import org.deephacks.westty.protobuf.CreateMessages.AsyncCreateRequest; import org.deephacks.westty.protobuf.CreateMessages.CreateExceptionRequest; import org.deephacks.westty.protobuf.CreateMessages.CreateRequest; import org.deephacks.westty.protobuf.CreateMessages.CreateResponse; import org.deephacks.westty.protobuf.CreateMessages.GetRequest; import org.deephacks.westty.protobuf.CreateMessages.NullRequest; import org.deephacks.westty.protobuf.DeleteMessages.DeleteRequest; import org.deephacks.westty.protobuf.DeleteMessages.DeleteResponse; import org.deephacks.westty.test.WesttyJUnit4Runner; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import javax.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @RunWith(WesttyJUnit4Runner.class) public class ProtobufTest { @Inject /** make sure that we can use injection for the client */ private ProtobufClient client; /** the id of the channel used by the tests */ private int channelId; /** * Client connect is tested here. */ @Before public void before() throws Exception { channelId = client.connect(); } /** * Client disconnect is tested here. */ @After public void after() throws Exception { client.disconnect(channelId); } /** * Test a sync endpoint invocation. */ @Test public void test_sync_create() throws Exception { String msg = "msg"; CreateRequest req = CreateRequest.newBuilder() .setMsg(msg) .build(); CreateResponse res = (CreateResponse) client.callSync(channelId, req); assertThat(res.getMsg(), is(msg)); } /** * Test that methods can be called async, verify that the endpoint * really got the message by fetching it shortly after the request. */ @Test public void test_async_create() throws Exception { String msg = "msg"; AsyncCreateRequest req = AsyncCreateRequest.newBuilder() .setMsg(msg) .build(); client.callAsync(channelId, req); Thread.sleep(500); GetRequest get = GetRequest.newBuilder().build(); req = (AsyncCreateRequest) client.callSync(channelId, get); assertThat(req.getMsg(), is(msg)); } /** * Test that sync endpoints can be invoked async, the * response will be ignored. */ @Test public void test_async_sync_create() throws Exception { String msg = "msg"; CreateRequest req = CreateRequest.newBuilder() .setMsg(msg) .build(); client.callAsync(channelId, req); Thread.sleep(500); GetRequest get = GetRequest.newBuilder().build(); req = (CreateRequest) client.callSync(channelId, get); assertThat(req.getMsg(), is(msg)); } /** * Test that a endpoint that return a null response will * deliver a void message that will be transalted to null * for the client. */ @Test public void test_null_response() throws Exception { NullRequest req = NullRequest.newBuilder() .build(); Object res = client.callSync(channelId, req); assertNull(res); } /** * Test that proto messages can be declared in multiple .proto and .desc files. * * (The delete message is declared in a separated .proto file) */ @Test public void test_delete() throws Exception { String msg = "msg"; DeleteRequest req = DeleteRequest.newBuilder() .setMsg(msg) .build(); DeleteResponse res = (DeleteResponse) client.callSync(channelId, req); assertThat(res.getMsg(), is(msg)); } /** * Test that exceptions a delivered as FailureMessages that will be translated * to a FailureMessageException to the client and that the correct error code * is delivered with it. */ @Test public void test_exception() throws Exception { String name = "name"; CreateExceptionRequest req = CreateExceptionRequest.newBuilder() .setMsg(name) .build(); try { client.callSync(channelId, req); fail("This invocation should cause an exception"); } catch (FailureMessageException e) { assertThat(e.getCode(), is(Integer.MIN_VALUE)); } } /** * Test that multiple clients can connect through same ProtobufClient * and that concurrent communication with endpoints deliver callbacks * to the correct clients. */ @Test public void test_multiple_clients() throws Exception { int numClients = 20; Executor executor = Executors.newFixedThreadPool(numClients); List<Client> clients = new ArrayList<>(numClients); CountDownLatch latch = new CountDownLatch(numClients); for (int i = 0; i < numClients; i++) { Client c = new Client(client); clients.add(c); executor.execute(c.connect(latch)); } latch.await(); int numMessages = 1000; latch = new CountDownLatch(numClients * numMessages); for (int i = 0; i < numMessages; i++) { for (Client c : clients){ executor.execute(c.sendMessage(latch)); } } latch.await(); latch = new CountDownLatch(numClients); StringBuilder failureMessages = new StringBuilder(); for (Client c : clients) { if(c.getIncorrectMessage().isPresent()){ failureMessages.append(c.getIncorrectMessage().get()); } executor.execute(c.disconnect(latch)); } if(failureMessages.length() > 0){ fail(failureMessages.toString()); } } public static class Client { private ProtobufClient client; private int channelId; private String id = UUID.randomUUID().toString(); private AtomicLong counter = new AtomicLong(); private Optional<String> incorrectMessage = Optional.absent(); public Client(ProtobufClient client){ this.client = client; } public Optional<String> getIncorrectMessage(){ return incorrectMessage; } public Runnable connect(final CountDownLatch latch) throws IOException { return new Runnable() { @Override public void run() { try { channelId = client.connect(); latch.countDown(); } catch (Exception e) { throw new RuntimeException(e); } } }; } public Runnable disconnect(final CountDownLatch latch) { return new Runnable() { @Override public void run() { client.disconnect(channelId); } }; } /** * Send a message and make assert that we got the correct response back * from the callback. */ public Runnable sendMessage(final CountDownLatch latch) throws IOException { return new Runnable() { @Override public void run() { String msg = id + counter.incrementAndGet(); CreateRequest req = CreateRequest.newBuilder() .setMsg(msg) .build(); try { CreateResponse res = (CreateResponse) client.callSync(channelId, req); latch.countDown(); if(!res.getMsg().equals(msg)){ String message = " Expected " + msg + " but got " + res.getMsg() + ". "; incorrectMessage = Optional.of(message); } } catch (IOException e) { throw new RuntimeException(e); } } }; } } }