// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.
// =================================================================================================
package com.twitter.common.thrift;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.collect.ImmutableSet;
import com.google.common.testing.TearDown;
import com.google.common.testing.junit4.TearDownTestCase;
import org.apache.thrift.async.AsyncMethodCallback;
import org.apache.thrift.async.TAsyncClient;
import org.apache.thrift.async.TAsyncClientManager;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.TNonblockingTransport;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.junit.Before;
import org.junit.Test;
import com.twitter.common.net.pool.DynamicHostSet;
import com.twitter.common.thrift.ThriftFactoryTest.GoodService.AsyncIface;
import com.twitter.thrift.ServiceInstance;
/**
* @author John Sirois
*/
public class ThriftFactoryTest extends TearDownTestCase {
private static final Logger LOG = Logger.getLogger(ThriftFactoryTest.class.getName());
private IMocksControl control;
static class GoodService {
public interface Iface {
String doWork() throws TResourceExhaustedException;
}
public interface AsyncIface {
void doWork(AsyncMethodCallback<String> callback);
}
public static final String DONE = "done";
public static class Client implements Iface {
public Client(TProtocol protocol) {
assertNotNull(protocol);
}
@Override public String doWork() throws TResourceExhaustedException {
return DONE;
}
}
public static class AsyncClient extends TAsyncClient implements AsyncIface {
public AsyncClient(TProtocolFactory factory, TAsyncClientManager manager,
TNonblockingTransport transport) {
super(factory, manager, transport);
assertNotNull(factory);
assertNotNull(manager);
assertNotNull(transport);
}
@Override public void doWork(AsyncMethodCallback<String> callback) {
callback.onComplete(DONE);
}
}
}
static class BadService {
public interface Iface {
void doWork();
}
public interface AsyncIface {
void doWork(AsyncMethodCallback<Void> callback);
}
public static class Client implements Iface {
@Override public void doWork() {
throw new UnsupportedOperationException();
}
}
}
private ImmutableSet<InetSocketAddress> endpoints;
@Before
public void setUp() throws Exception {
control = EasyMock.createControl();
endpoints = ImmutableSet.of(new InetSocketAddress(5555));
}
@Test(expected = NullPointerException.class)
public void testNullServiceInterface() {
ThriftFactory.create(null);
}
@Test(expected = IllegalArgumentException.class)
public void testBadServiceInterface() {
ThriftFactory.create(GoodService.class);
}
@Test(expected = IllegalArgumentException.class)
public void testBadServiceImpl() throws ThriftFactory.ThriftFactoryException {
ThriftFactory.<BadService.Iface>create(BadService.Iface.class)
.build(endpoints);
}
@Test(expected = IllegalArgumentException.class)
public void testBadAsyncServiceImpl() throws ThriftFactory.ThriftFactoryException {
ThriftFactory.<BadService.AsyncIface>create(BadService.AsyncIface.class)
.useFramedTransport(true)
.buildAsync(endpoints);
}
@Test(expected = IllegalArgumentException.class)
public void testNoBackends() {
ThriftFactory.create(GoodService.Iface.class)
.build(ImmutableSet.<InetSocketAddress>of());
}
@Test
public void testCreate() throws Exception {
final AtomicReference<Socket> clientConnection = new AtomicReference<Socket>();
final CountDownLatch connected = new CountDownLatch(1);
final ServerSocket server = new ServerSocket(0);
Thread service = new Thread(new Runnable() {
@Override public void run() {
try {
clientConnection.set(server.accept());
} catch (IOException e) {
LOG.log(Level.WARNING, "Problem accepting a connection to thrift server", e);
} finally {
connected.countDown();
}
}
});
service.setDaemon(true);
service.start();
try {
final Thrift<GoodService.Iface> thrift = ThriftFactory.create(GoodService.Iface.class)
.withMaxConnectionsPerEndpoint(1)
.build(ImmutableSet.of(new InetSocketAddress(server.getLocalPort())));
addTearDown(new TearDown() {
@Override public void tearDown() {
thrift.close();
}
});
GoodService.Iface client = thrift.create();
assertEquals(GoodService.DONE, client.doWork());
} finally {
connected.await();
server.close();
}
Socket socket = clientConnection.get();
assertNotNull(socket);
socket.close();
}
@Test(expected = TResourceExhaustedException.class)
public void testCreateEmpty() throws Exception {
@SuppressWarnings("unchecked")
DynamicHostSet<ServiceInstance> emptyHostSet = control.createMock(DynamicHostSet.class);
final Thrift<GoodService.Iface> thrift = ThriftFactory.create(GoodService.Iface.class)
.withMaxConnectionsPerEndpoint(1)
.build(emptyHostSet);
addTearDown(new TearDown() {
@Override public void tearDown() {
thrift.close();
}
});
GoodService.Iface client = thrift.create();
// This should throw a TResourceExhaustedException
client.doWork();
}
@Test
public void testCreateAsync()
throws IOException, InterruptedException, ThriftFactory.ThriftFactoryException {
final String responseHolder[] = new String[] {null};
final CountDownLatch done = new CountDownLatch(1);
AsyncMethodCallback<String> callback = new AsyncMethodCallback<String>() {
@Override
public void onComplete(String response) {
responseHolder[0] = response;
done.countDown();
}
@Override
public void onError(Throwable throwable) {
responseHolder[0] = throwable.toString();
done.countDown();
}
};
final Thrift<AsyncIface> thrift = ThriftFactory.create(GoodService.AsyncIface.class)
.withMaxConnectionsPerEndpoint(1)
.useFramedTransport(true)
.buildAsync(ImmutableSet.of(new InetSocketAddress(1234)));
addTearDown(new TearDown() {
@Override public void tearDown() {
thrift.close();
}
});
GoodService.AsyncIface client = thrift.builder()
.blocking()
.create();
client.doWork(callback);
assertTrue("wasn't called back in time, callback got " + responseHolder[0],
done.await(5000, TimeUnit.MILLISECONDS));
assertEquals(GoodService.DONE, responseHolder[0]);
}
}