/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 */

/*
 * This file contains tests that ensure TProcessorEventHandler and
 * TServerEventHandler are invoked properly by the various server
 * implementations.
 */

#include <boost/test/unit_test.hpp>

#include <thrift/concurrency/ThreadFactory.h>
#include <thrift/concurrency/Monitor.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/server/TNonblockingServer.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TNonblockingServerSocket.h>

#include "EventLog.h"
#include "ServerThread.h"
#include "Handlers.h"
#include "gen-cpp/ChildService.h"

using namespace apache::thrift;
using namespace apache::thrift::concurrency;
using namespace apache::thrift::protocol;
using namespace apache::thrift::server;
using namespace apache::thrift::test;
using namespace apache::thrift::transport;
using std::string;
using std::vector;

/*
 * Traits classes that encapsulate how to create various types of servers.
 */

class TSimpleServerTraits {
public:
  typedef TSimpleServer ServerType;

  std::shared_ptr<TSimpleServer> createServer(
      const std::shared_ptr<TProcessor>& processor,
      uint16_t port,
      const std::shared_ptr<TTransportFactory>& transportFactory,
      const std::shared_ptr<TProtocolFactory>& protocolFactory) {
    std::shared_ptr<TServerSocket> socket(new TServerSocket(port));
    return std::shared_ptr<TSimpleServer>(
        new TSimpleServer(processor, socket, transportFactory, protocolFactory));
  }
};

class TThreadedServerTraits {
public:
  typedef TThreadedServer ServerType;

  std::shared_ptr<TThreadedServer> createServer(
      const std::shared_ptr<TProcessor>& processor,
      uint16_t port,
      const std::shared_ptr<TTransportFactory>& transportFactory,
      const std::shared_ptr<TProtocolFactory>& protocolFactory) {
    std::shared_ptr<TServerSocket> socket(new TServerSocket(port));
    return std::shared_ptr<TThreadedServer>(
        new TThreadedServer(processor, socket, transportFactory, protocolFactory));
  }
};

class TThreadPoolServerTraits {
public:
  typedef TThreadPoolServer ServerType;

  std::shared_ptr<TThreadPoolServer> createServer(
      const std::shared_ptr<TProcessor>& processor,
      uint16_t port,
      const std::shared_ptr<TTransportFactory>& transportFactory,
      const std::shared_ptr<TProtocolFactory>& protocolFactory) {
    std::shared_ptr<TServerSocket> socket(new TServerSocket(port));

    std::shared_ptr<ThreadFactory> threadFactory(new ThreadFactory);
    std::shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(8);
    threadManager->threadFactory(threadFactory);
    threadManager->start();

    return std::shared_ptr<TThreadPoolServer>(
        new TThreadPoolServer(processor, socket, transportFactory, protocolFactory, threadManager));
  }
};

class TNonblockingServerTraits {
public:
  typedef TNonblockingServer ServerType;

  std::shared_ptr<TNonblockingServer> createServer(
      const std::shared_ptr<TProcessor>& processor,
      uint16_t port,
      const std::shared_ptr<TTransportFactory>& transportFactory,
      const std::shared_ptr<TProtocolFactory>& protocolFactory) {
    // TNonblockingServer automatically uses TFramedTransport.
    // Raise an exception if the supplied transport factory is not a
    // TFramedTransportFactory
    auto* framedFactory
        = dynamic_cast<TFramedTransportFactory*>(transportFactory.get());
    if (framedFactory == nullptr) {
      throw TException("TNonblockingServer must use TFramedTransport");
    }

    std::shared_ptr<TNonblockingServerSocket> socket(new TNonblockingServerSocket(port));
    std::shared_ptr<ThreadFactory> threadFactory(new ThreadFactory);
    std::shared_ptr<ThreadManager> threadManager = ThreadManager::newSimpleThreadManager(8);
    threadManager->threadFactory(threadFactory);
    threadManager->start();

    return std::shared_ptr<TNonblockingServer>(
        new TNonblockingServer(processor, protocolFactory, socket, threadManager));
  }
};

class TNonblockingServerNoThreadsTraits {
public:
  typedef TNonblockingServer ServerType;

  std::shared_ptr<TNonblockingServer> createServer(
      const std::shared_ptr<TProcessor>& processor,
      uint16_t port,
      const std::shared_ptr<TTransportFactory>& transportFactory,
      const std::shared_ptr<TProtocolFactory>& protocolFactory) {
    // TNonblockingServer automatically uses TFramedTransport.
    // Raise an exception if the supplied transport factory is not a
    // TFramedTransportFactory
    auto* framedFactory
        = dynamic_cast<TFramedTransportFactory*>(transportFactory.get());
    if (framedFactory == nullptr) {
      throw TException("TNonblockingServer must use TFramedTransport");
    }

    std::shared_ptr<TNonblockingServerSocket> socket(new TNonblockingServerSocket(port));
    // Use a NULL ThreadManager
    std::shared_ptr<ThreadManager> threadManager;
    return std::shared_ptr<TNonblockingServer>(
        new TNonblockingServer(processor, protocolFactory, socket, threadManager));
  }
};

/*
 * Traits classes for controlling if we instantiate templated or generic
 * protocol factories, processors, clients, etc.
 *
 * The goal is to allow the outer test code to select which server type is
 * being tested, and whether or not we are testing the templated classes, or
 * the generic classes.
 *
 * Each specific test case can control whether we create a child or parent
 * server, and whether we use TFramedTransport or TBufferedTransport.
 */

class UntemplatedTraits {
public:
  typedef TBinaryProtocolFactory ProtocolFactory;
  typedef TBinaryProtocol Protocol;

  typedef ParentServiceProcessor ParentProcessor;
  typedef ChildServiceProcessor ChildProcessor;
  typedef ParentServiceClient ParentClient;
  typedef ChildServiceClient ChildClient;
};

class TemplatedTraits {
public:
  typedef TBinaryProtocolFactoryT<TBufferBase> ProtocolFactory;
  typedef TBinaryProtocolT<TBufferBase> Protocol;

  typedef ParentServiceProcessorT<Protocol> ParentProcessor;
  typedef ChildServiceProcessorT<Protocol> ChildProcessor;
  typedef ParentServiceClientT<Protocol> ParentClient;
  typedef ChildServiceClientT<Protocol> ChildClient;
};

template <typename TemplateTraits_>
class ParentServiceTraits {
public:
  typedef typename TemplateTraits_::ParentProcessor Processor;
  typedef typename TemplateTraits_::ParentClient Client;
  typedef ParentHandler Handler;

  typedef typename TemplateTraits_::ProtocolFactory ProtocolFactory;
  typedef typename TemplateTraits_::Protocol Protocol;
};

template <typename TemplateTraits_>
class ChildServiceTraits {
public:
  typedef typename TemplateTraits_::ChildProcessor Processor;
  typedef typename TemplateTraits_::ChildClient Client;
  typedef ChildHandler Handler;

  typedef typename TemplateTraits_::ProtocolFactory ProtocolFactory;
  typedef typename TemplateTraits_::Protocol Protocol;
};

// TODO: It would be nicer if the TTransportFactory types defined a typedef,
// to allow us to figure out the exact transport type without having to pass it
// in as a separate template parameter here.
//
// It would also be niec if they used covariant return types.  Unfortunately,
// since they return shared_ptr instead of raw pointers, covariant return types
// won't work.
template <typename ServerTraits_,
          typename ServiceTraits_,
          typename TransportFactory_ = TFramedTransportFactory,
          typename Transport_ = TFramedTransport>
class ServiceState : public ServerState {
public:
  typedef typename ServiceTraits_::Processor Processor;
  typedef typename ServiceTraits_::Client Client;
  typedef typename ServiceTraits_::Handler Handler;

  ServiceState()
    : port_(0),
      log_(new EventLog),
      handler_(new Handler(log_)),
      processor_(new Processor(handler_)),
      transportFactory_(new TransportFactory_),
      protocolFactory_(new typename ServiceTraits_::ProtocolFactory),
      serverEventHandler_(new ServerEventHandler(log_)),
      processorEventHandler_(new ProcessorEventHandler(log_)) {
    processor_->setEventHandler(processorEventHandler_);
  }

  std::shared_ptr<TServer> createServer(uint16_t port) override {
    ServerTraits_ serverTraits;
    return serverTraits.createServer(processor_, port, transportFactory_, protocolFactory_);
  }

  std::shared_ptr<TServerEventHandler> getServerEventHandler() override { return serverEventHandler_; }

  void bindSuccessful(uint16_t port) override { port_ = port; }

  uint16_t getPort() const { return port_; }

  const std::shared_ptr<EventLog>& getLog() const { return log_; }

  const std::shared_ptr<Handler>& getHandler() const { return handler_; }

  std::shared_ptr<Client> createClient() {
    typedef typename ServiceTraits_::Protocol Protocol;

    std::shared_ptr<TSocket> socket(new TSocket("127.0.0.1", port_));
    std::shared_ptr<Transport_> transport(new Transport_(socket));
    std::shared_ptr<Protocol> protocol(new Protocol(transport));
    transport->open();

    std::shared_ptr<Client> client(new Client(protocol));
    return client;
  }

private:
  uint16_t port_;
  std::shared_ptr<EventLog> log_;
  std::shared_ptr<Handler> handler_;
  std::shared_ptr<Processor> processor_;
  std::shared_ptr<TTransportFactory> transportFactory_;
  std::shared_ptr<TProtocolFactory> protocolFactory_;
  std::shared_ptr<TServerEventHandler> serverEventHandler_;
  std::shared_ptr<TProcessorEventHandler> processorEventHandler_;
};

/**
 * Check that there are no more events in the log
 */
void checkNoEvents(const std::shared_ptr<EventLog>& log) {
  // Wait for an event with a very short timeout period.  We don't expect
  // anything to be present, so we will normally wait for the full timeout.
  // On the other hand, a non-zero timeout is nice since it does give a short
  // window for events to arrive in case there is a problem.
  Event event = log->waitForEvent(10);
  BOOST_CHECK_EQUAL(EventLog::ET_LOG_END, event.type);
}

/**
 * Check for the events that should be logged when a new connection is created.
 *
 * Returns the connection ID allocated by the server.
 */
uint32_t checkNewConnEvents(const std::shared_ptr<EventLog>& log) {
  // Check for an ET_CONN_CREATED event
  Event event = log->waitForEvent(2500);
  BOOST_CHECK_EQUAL(EventLog::ET_CONN_CREATED, event.type);

  // Some servers call the processContext() hook immediately.
  // Others (TNonblockingServer) only call it once a full request is received.
  // We don't check for it yet, to allow either behavior.

  return event.connectionId;
}

/**
 * Check for the events that should be logged when a connection is closed.
 */
void checkCloseEvents(const std::shared_ptr<EventLog>& log, uint32_t connId) {
  // Check for an ET_CONN_DESTROYED event
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CONN_DESTROYED, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);

  // Make sure there are no more events
  checkNoEvents(log);
}

/**
 * Check for the events that should be logged when a call is received
 * and the handler is invoked.
 *
 * It does not check for anything after the handler invocation.
 *
 * Returns the call ID allocated by the server.
 */
uint32_t checkCallHandlerEvents(const std::shared_ptr<EventLog>& log,
                                uint32_t connId,
                                EventType callType,
                                const string& callName) {
  // Call started
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_STARTED, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callName, event.message);
  uint32_t callId = event.callId;

  // Pre-read
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_PRE_READ, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // Post-read
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_POST_READ, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // Handler invocation
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(callType, event.type);
  // The handler doesn't have any connection or call context,
  // so the connectionId and callId in this event aren't valid

  return callId;
}

/**
 * Check for the events that should be after a handler returns.
 */
void checkCallPostHandlerEvents(const std::shared_ptr<EventLog>& log,
                                uint32_t connId,
                                uint32_t callId,
                                const string& callName) {
  // Pre-write
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_PRE_WRITE, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // Post-write
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_POST_WRITE, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // Call finished
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // It is acceptable for servers to call processContext() again immediately
  // to start waiting on the next request.  However, some servers wait before
  // getting either a partial request or the full request before calling
  // processContext().  We don't check for the next call to processContext()
  // yet.
}

/**
 * Check for the events that should be logged when a call is made.
 *
 * This just calls checkCallHandlerEvents() followed by
 * checkCallPostHandlerEvents().
 *
 * Returns the call ID allocated by the server.
 */
uint32_t checkCallEvents(const std::shared_ptr<EventLog>& log,
                         uint32_t connId,
                         EventType callType,
                         const string& callName) {
  uint32_t callId = checkCallHandlerEvents(log, connId, callType, callName);
  checkCallPostHandlerEvents(log, connId, callId, callName);

  return callId;
}

/*
 * Test functions
 */

template <typename State_>
void testParentService(const std::shared_ptr<State_>& state) {
  std::shared_ptr<typename State_::Client> client = state->createClient();

  int32_t gen = client->getGeneration();
  int32_t newGen = client->incrementGeneration();
  BOOST_CHECK_EQUAL(gen + 1, newGen);
  newGen = client->getGeneration();
  BOOST_CHECK_EQUAL(gen + 1, newGen);

  client->addString("foo");
  client->addString("bar");
  client->addString("asdf");

  vector<string> strings;
  client->getStrings(strings);
  BOOST_REQUIRE_EQUAL(3, strings.size());
  BOOST_REQUIRE_EQUAL("foo", strings[0]);
  BOOST_REQUIRE_EQUAL("bar", strings[1]);
  BOOST_REQUIRE_EQUAL("asdf", strings[2]);
}

template <typename State_>
void testChildService(const std::shared_ptr<State_>& state) {
  std::shared_ptr<typename State_::Client> client = state->createClient();

  // Test calling some of the parent methids via the a child client
  int32_t gen = client->getGeneration();
  int32_t newGen = client->incrementGeneration();
  BOOST_CHECK_EQUAL(gen + 1, newGen);
  newGen = client->getGeneration();
  BOOST_CHECK_EQUAL(gen + 1, newGen);

  // Test some of the child methods
  client->setValue(10);
  BOOST_CHECK_EQUAL(10, client->getValue());
  BOOST_CHECK_EQUAL(10, client->setValue(99));
  BOOST_CHECK_EQUAL(99, client->getValue());
}

template <typename ServerTraits, typename TemplateTraits>
void testBasicService() {
  typedef ServiceState<ServerTraits, ParentServiceTraits<TemplateTraits> > State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  testParentService(state);
}

template <typename ServerTraits, typename TemplateTraits>
void testInheritedService() {
  typedef ServiceState<ServerTraits, ChildServiceTraits<TemplateTraits> > State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  testParentService(state);
  testChildService(state);
}

/**
 * Test to make sure that the TServerEventHandler and TProcessorEventHandler
 * methods are invoked in the correct order with the actual events.
 */
template <typename ServerTraits, typename TemplateTraits>
void testEventSequencing() {
  // We use TBufferedTransport for this test, instead of TFramedTransport.
  // This way the server will start processing data as soon as it is received,
  // instead of waiting for the full request.  This is necessary so we can
  // separate the preRead() and postRead() events.
  typedef ServiceState<ServerTraits,
                       ChildServiceTraits<TemplateTraits>,
                       TBufferedTransportFactory,
                       TBufferedTransport> State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  const std::shared_ptr<EventLog>& log = state->getLog();

  // Make sure we're at the end of the log
  checkNoEvents(log);

  state->getHandler()->prepareTriggeredCall();

  // Make sure createContext() is called after a connection has been
  // established.  We open a plain socket instead of creating a client.
  std::shared_ptr<TSocket> socket(new TSocket("127.0.0.1", state->getPort()));
  socket->open();

  // Make sure the proper events occurred after a new connection
  uint32_t connId = checkNewConnEvents(log);

  // Send a message header.  We manually construct the request so that we
  // can test the timing for the preRead() call.
  string requestName = "getDataWait";
  string eventName = "ParentService.getDataWait";
  auto seqid = int32_t(time(nullptr));
  TBinaryProtocol protocol(socket);
  protocol.writeMessageBegin(requestName, T_CALL, seqid);
  socket->flush();

  // Make sure we saw the call started and pre-read events
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_STARTED, event.type);
  BOOST_CHECK_EQUAL(eventName, event.message);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  uint32_t callId = event.callId;

  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_PRE_READ, event.type);
  BOOST_CHECK_EQUAL(eventName, event.message);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);

  // Make sure there are no new events
  checkNoEvents(log);

  // Send the rest of the request
  protocol.writeStructBegin("ParentService_getDataNotified_pargs");
  protocol.writeFieldBegin("length", apache::thrift::protocol::T_I32, 1);
  protocol.writeI32(8 * 1024 * 1024);
  protocol.writeFieldEnd();
  protocol.writeFieldStop();
  protocol.writeStructEnd();
  protocol.writeMessageEnd();
  socket->writeEnd();
  socket->flush();

  // We should then see postRead()
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_POST_READ, event.type);
  BOOST_CHECK_EQUAL(eventName, event.message);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);

  // Then the handler should be invoked
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_GET_DATA_WAIT, event.type);

  // The handler won't respond until we notify it.
  // Make sure there are no more events.
  checkNoEvents(log);

  // Notify the handler that it should return
  // We just use a global lock for now, since it is easiest
  state->getHandler()->triggerPendingCalls();

  // The handler will log a separate event before it returns
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type);

  // We should then see preWrite()
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_PRE_WRITE, event.type);
  BOOST_CHECK_EQUAL(eventName, event.message);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);

  // We requested more data than can be buffered, and we aren't reading it,
  // so the server shouldn't be able to finish its write yet.
  // Make sure there are no more events.
  checkNoEvents(log);

  // Read the response header
  string responseName;
  int32_t responseSeqid = 0;
  apache::thrift::protocol::TMessageType responseType;
  protocol.readMessageBegin(responseName, responseType, responseSeqid);
  BOOST_CHECK_EQUAL(responseSeqid, seqid);
  BOOST_CHECK_EQUAL(requestName, responseName);
  BOOST_CHECK_EQUAL(responseType, T_REPLY);
  // Read the body.  We just ignore it for now.
  protocol.skip(T_STRUCT);

  // Now that we have read, the server should have finished sending the data
  // and called the postWrite() handler
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_POST_WRITE, event.type);
  BOOST_CHECK_EQUAL(eventName, event.message);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);

  // Call finished should be last
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type);
  BOOST_CHECK_EQUAL(eventName, event.message);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);

  // There should be no more events
  checkNoEvents(log);

  // Close the connection, and make sure we get a connection destroyed event
  socket->close();
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CONN_DESTROYED, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);

  // There should be no more events
  checkNoEvents(log);
}

template <typename ServerTraits, typename TemplateTraits>
void testSeparateConnections() {
  typedef ServiceState<ServerTraits, ChildServiceTraits<TemplateTraits> > State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  const std::shared_ptr<EventLog>& log = state->getLog();

  // Create a client
  std::shared_ptr<typename State::Client> client1 = state->createClient();

  // Make sure the expected events were logged
  uint32_t client1Id = checkNewConnEvents(log);

  // Create a second client
  std::shared_ptr<typename State::Client> client2 = state->createClient();

  // Make sure the expected events were logged
  uint32_t client2Id = checkNewConnEvents(log);

  // The two connections should have different IDs
  BOOST_CHECK_NE(client1Id, client2Id);

  // Make a call, and check for the proper events
  int32_t value = 5;
  client1->setValue(value);
  uint32_t call1
      = checkCallEvents(log, client1Id, EventLog::ET_CALL_SET_VALUE, "ChildService.setValue");

  // Make a call with client2
  int32_t v = client2->getValue();
  BOOST_CHECK_EQUAL(value, v);
  checkCallEvents(log, client2Id, EventLog::ET_CALL_GET_VALUE, "ChildService.getValue");

  // Make another call with client1
  v = client1->getValue();
  BOOST_CHECK_EQUAL(value, v);
  uint32_t call2
      = checkCallEvents(log, client1Id, EventLog::ET_CALL_GET_VALUE, "ChildService.getValue");
  BOOST_CHECK_NE(call1, call2);

  // Close the second client, and check for the appropriate events
  client2.reset();
  checkCloseEvents(log, client2Id);
}

template <typename ServerTraits, typename TemplateTraits>
void testOnewayCall() {
  typedef ServiceState<ServerTraits, ChildServiceTraits<TemplateTraits> > State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  const std::shared_ptr<EventLog>& log = state->getLog();

  // Create a client
  std::shared_ptr<typename State::Client> client = state->createClient();
  uint32_t connId = checkNewConnEvents(log);

  // Make a oneway call
  // It should return immediately, even though the server's handler
  // won't return right away
  state->getHandler()->prepareTriggeredCall();
  client->onewayWait();
  string callName = "ParentService.onewayWait";
  uint32_t callId = checkCallHandlerEvents(log, connId, EventLog::ET_CALL_ONEWAY_WAIT, callName);

  // There shouldn't be any more events
  checkNoEvents(log);

  // Trigger the handler to return
  state->getHandler()->triggerPendingCalls();

  // The handler will log an ET_WAIT_RETURN event when it wakes up
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type);

  // Now we should see the async complete event, then call finished
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_ASYNC_COMPLETE, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // Destroy the client, and check for connection closed events
  client.reset();
  checkCloseEvents(log, connId);

  checkNoEvents(log);
}

template <typename ServerTraits, typename TemplateTraits>
void testExpectedError() {
  typedef ServiceState<ServerTraits, ChildServiceTraits<TemplateTraits> > State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  const std::shared_ptr<EventLog>& log = state->getLog();

  // Create a client
  std::shared_ptr<typename State::Client> client = state->createClient();
  uint32_t connId = checkNewConnEvents(log);

  // Send the exceptionWait() call
  state->getHandler()->prepareTriggeredCall();
  string message = "test 1234 test";
  client->send_exceptionWait(message);
  string callName = "ParentService.exceptionWait";
  uint32_t callId = checkCallHandlerEvents(log, connId, EventLog::ET_CALL_EXCEPTION_WAIT, callName);

  // There shouldn't be any more events
  checkNoEvents(log);

  // Trigger the handler to return
  state->getHandler()->triggerPendingCalls();

  // The handler will log an ET_WAIT_RETURN event when it wakes up
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type);

  // Now receive the response
  try {
    client->recv_exceptionWait();
    BOOST_FAIL("expected MyError to be thrown");
  } catch (const MyError& e) {
    BOOST_CHECK_EQUAL(message, e.message);
    // Check if std::exception::what() is handled properly
    size_t message_pos = string(e.what()).find("TException - service has thrown: MyError");
    BOOST_CHECK_NE(message_pos, string::npos);
  }

  // Now we should see the events for a normal call finish
  checkCallPostHandlerEvents(log, connId, callId, callName);

  // There shouldn't be any more events
  checkNoEvents(log);

  // Destroy the client, and check for connection closed events
  client.reset();
  checkCloseEvents(log, connId);

  checkNoEvents(log);
}

template <typename ServerTraits, typename TemplateTraits>
void testUnexpectedError() {
  typedef ServiceState<ServerTraits, ChildServiceTraits<TemplateTraits> > State;

  // Start the server
  std::shared_ptr<State> state(new State);
  ServerThread serverThread(state, true);

  const std::shared_ptr<EventLog>& log = state->getLog();

  // Create a client
  std::shared_ptr<typename State::Client> client = state->createClient();
  uint32_t connId = checkNewConnEvents(log);

  // Send the unexpectedExceptionWait() call
  state->getHandler()->prepareTriggeredCall();
  string message = "1234 test 5678";
  client->send_unexpectedExceptionWait(message);
  string callName = "ParentService.unexpectedExceptionWait";
  uint32_t callId
      = checkCallHandlerEvents(log, connId, EventLog::ET_CALL_UNEXPECTED_EXCEPTION_WAIT, callName);

  // There shouldn't be any more events
  checkNoEvents(log);

  // Trigger the handler to return
  state->getHandler()->triggerPendingCalls();

  // The handler will log an ET_WAIT_RETURN event when it wakes up
  Event event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type);

  // Now receive the response
  try {
    client->recv_unexpectedExceptionWait();
    BOOST_FAIL("expected TApplicationError to be thrown");
  } catch (const TApplicationException&) {
  }

  // Now we should see a handler error event
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_HANDLER_ERROR, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // pre-write and post-write events aren't generated after a handler error
  // (Even for non-oneway calls where a response is written.)
  //
  // A call finished event is logged when the call context is destroyed
  event = log->waitForEvent();
  BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type);
  BOOST_CHECK_EQUAL(connId, event.connectionId);
  BOOST_CHECK_EQUAL(callId, event.callId);
  BOOST_CHECK_EQUAL(callName, event.message);

  // There shouldn't be any more events
  checkNoEvents(log);

  // Destroy the client, and check for connection closed events
  client.reset();
  checkCloseEvents(log, connId);

  checkNoEvents(log);
}

// Macro to define simple tests that can be used with all server types
#define DEFINE_SIMPLE_TESTS(Server, Template)                                                      \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_basicService) {                                       \
    testBasicService<Server##Traits, Template##Traits>();                                          \
  }                                                                                                \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_inheritedService) {                                   \
    testInheritedService<Server##Traits, Template##Traits>();                                      \
  }                                                                                                \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_oneway) {                                             \
    testOnewayCall<Server##Traits, Template##Traits>();                                            \
  }                                                                                                \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_exception) {                                          \
    testExpectedError<Server##Traits, Template##Traits>();                                         \
  }                                                                                                \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_unexpectedException) {                                \
    testUnexpectedError<Server##Traits, Template##Traits>();                                       \
  }

// Tests that require the server to process multiple connections concurrently
// (i.e., not TSimpleServer)
#define DEFINE_CONCURRENT_SERVER_TESTS(Server, Template)                                           \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_separateConnections) {                                \
    testSeparateConnections<Server##Traits, Template##Traits>();                                   \
  }

// The testEventSequencing() test manually generates a request for the server,
// and doesn't work with TFramedTransport.  Therefore we can't test it with
// TNonblockingServer.
#define DEFINE_NOFRAME_TESTS(Server, Template)                                                     \
  BOOST_AUTO_TEST_CASE(Server##_##Template##_eventSequencing) {                                    \
    testEventSequencing<Server##Traits, Template##Traits>();                                       \
  }

#define DEFINE_TNONBLOCKINGSERVER_TESTS(Server, Template)                                          \
  DEFINE_SIMPLE_TESTS(Server, Template)                                                            \
  DEFINE_CONCURRENT_SERVER_TESTS(Server, Template)

#define DEFINE_ALL_SERVER_TESTS(Server, Template)                                                  \
  DEFINE_SIMPLE_TESTS(Server, Template)                                                            \
  DEFINE_CONCURRENT_SERVER_TESTS(Server, Template)                                                 \
  DEFINE_NOFRAME_TESTS(Server, Template)

DEFINE_ALL_SERVER_TESTS(TThreadedServer, Templated)
DEFINE_ALL_SERVER_TESTS(TThreadedServer, Untemplated)
DEFINE_ALL_SERVER_TESTS(TThreadPoolServer, Templated)
DEFINE_ALL_SERVER_TESTS(TThreadPoolServer, Untemplated)

DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServer, Templated)
DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServer, Untemplated)
DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServerNoThreads, Templated)
DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServerNoThreads, Untemplated)

DEFINE_SIMPLE_TESTS(TSimpleServer, Templated)
DEFINE_SIMPLE_TESTS(TSimpleServer, Untemplated)
DEFINE_NOFRAME_TESTS(TSimpleServer, Templated)
DEFINE_NOFRAME_TESTS(TSimpleServer, Untemplated)

// TODO: We should test TEventServer in the future.
// For now, it is known not to work correctly with TProcessorEventHandler.
#ifdef BOOST_TEST_DYN_LINK
bool init_unit_test_suite() {
  ::boost::unit_test::framework::master_test_suite().p_name.value = "ProcessorTest";
  return true;
}

int main( int argc, char* argv[] ) {
  return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv);
}
#else
::boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
  THRIFT_UNUSED_VARIABLE(argc);
  THRIFT_UNUSED_VARIABLE(argv);
  ::boost::unit_test::framework::master_test_suite().p_name.value = "ProcessorTest";
  return nullptr;
}
#endif
