<commit_before>/*  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
 *
 *  Gearmand client and server library.
 *
 *  Copyright (C) 2012 Data Differential, http://datadifferential.com/
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *
 *      * Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *      * Redistributions in binary form must reproduce the above
 *  copyright notice, this list of conditions and the following disclaimer
 *  in the documentation and/or other materials provided with the
 *  distribution.
 *
 *      * The names of its contributors may not be used to endorse or
 *  promote products derived from this software without specific prior
 *  written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "gear_config.h"

#include <libtest/test.hpp>

using namespace libtest;

#include <libgearman/gearman.h>

#include <libhostile/hostile.h>

#include "tests/client.h"
#include <tests/start_worker.h>
#include "tests/burnin.h"

#include "tests/workers/v2/echo_or_react.h"

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/time.h>

#define WORKER_FUNCTION_NAME "foo"

static bool has_hostile()
{
#if defined(HAVE_LIBHOSTILE) && HAVE_LIBHOSTILE
  if (1)
  {
    return true;
  }
#endif

  return false;
}

static in_port_t hostile_server= 0;
static in_port_t stress_server= 0;
static in_port_t& current_server_= stress_server;

static void reset_server()
{
  current_server_= stress_server;
}

static in_port_t current_server()
{
  return current_server_;
}

static void set_server(in_port_t& arg)
{
  current_server_= arg;
}

struct client_thread_context_st
{
  size_t count;
  size_t payload_size;

  client_thread_context_st() :
    count(0),
    payload_size(0)
  { }

  void increment()
  {
    count++;
  }
};

extern "C" {
  static __attribute__((noreturn)) void *client_thread(void *object)
  {
    client_thread_context_st *success= (client_thread_context_st *)object;
    fatal_assert(success);
    fatal_assert(success->count == 0);

    Client client(current_server());
    {
      gearman_client_set_timeout(&client, 400);
      for (size_t x= 0; x < 100; x++)
      {
        int oldstate;
        pthread_setcanceltype(PTHREAD_CANCEL_DISABLE, &oldstate);
        libtest::vchar_t payload;
        payload.resize(success->payload_size);
        gearman_return_t rc;
        void *value= gearman_client_do(&client, WORKER_FUNCTION_NAME,
                                       NULL,
                                       &payload[0],
                                       payload.size() ? random() % payload.size() : 0,
                                       NULL, &rc);
        pthread_setcanceltype(oldstate, NULL);

        if (gearman_success(rc))
        {
          success->increment();
        }

        if (value)
        {
          free(value);
        }
      }
    }

    pthread_exit(0);
  }
}

static bool join_thread(pthread_t& thread_arg, struct timespec& ts)
{
  int error;
  ts.tv_sec+= 10;

  if (HAVE_PTHREAD_TIMEDJOIN_NP)
  {
#if defined(HAVE_PTHREAD_TIMEDJOIN_NP) && HAVE_PTHREAD_TIMEDJOIN_NP
    int limit= 2;
    while (--limit)
    {
      switch ((error= pthread_timedjoin_np(thread_arg, NULL, &ts)))
      {
      case ETIMEDOUT:
        libtest::dream(1, 0);
        continue;

      case 0:
        return true;

      case ESRCH:
        return false;

      default:
        Error << "pthread_timedjoin_np() " << strerror(error);
        return false;
      }
    }

    Out << "pthread_timedjoin_np() " << strerror(error);
    if ((error= pthread_cancel(thread_arg)) != 0)
    {
      Error << "pthread_cancel() " << strerror(error);
      return false;
    }
#endif

    return true;
  }

  if ((error= pthread_join(thread_arg, NULL)) != 0)
  {
    Error << "pthread_join() " << strerror(error);
    return false;
  }

  return true;
}

static test_return_t worker_ramp_exec(const size_t payload_size)
{
  set_alarm(1200, 0);

  std::vector<pthread_t> children;
  children.resize(number_of_cpus());

  std::vector<client_thread_context_st>  success;
  success.resize(children.size());

  for (size_t x= 0; x < children.size(); x++)
  {
    success[x].payload_size= payload_size;
    pthread_create(&children[x], NULL, client_thread, &success[x]);
  }

  for (size_t x= 0; x < children.size(); x++)
  {
    struct timespec ts;
    bool join_success= false;
    int limit= 2;
    while (join_success == false and --limit)
    {
#if defined(HAVE_LIBRT) && HAVE_LIBRT
      if (HAVE_LIBRT) // This won't be called on OSX, etc,...
      {
        if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
        {
          Error << "clock_gettime(CLOCK_REALTIME) " << strerror(errno);
          continue;
        }

        join_success= join_thread(children[x], ts);
      }
      else
#endif
      {
        struct timeval tv;
        if (gettimeofday(&tv, NULL) == -1)
        {
          Error << "gettimeofday() " << strerror(errno);
          continue;
        }

        TIMEVAL_TO_TIMESPEC(&tv, &ts);
        join_success= join_thread(children[x], ts);
      }
    }

    if (join_success == false)
    {
      pthread_cancel(children[x]);
      Error << "Something went very wrong, it is likely threads were not cleaned up";
    }
  }

  return TEST_SUCCESS;
}

static test_return_t worker_ramp_TEST(void *)
{
  return worker_ramp_exec(0);
}

static test_return_t worker_ramp_1K_TEST(void *)
{
  return worker_ramp_exec(1024);
}

static test_return_t worker_ramp_10K_TEST(void *)
{
  return worker_ramp_exec(1024*10);
}

static test_return_t worker_ramp_SETUP(void *object)
{
  test_skip_valgrind();

  worker_handles_st *handles= (worker_handles_st*)object;

  gearman_function_t echo_react_fn= gearman_function_create(echo_or_react_worker_v2);
  for (uint32_t x= 0; x < 10; x++)
  {
    worker_handle_st *worker;
    if ((worker= test_worker_start(current_server(), NULL, WORKER_FUNCTION_NAME, echo_react_fn, NULL, gearman_worker_options_t())) == NULL)
    {
      return TEST_FAILURE;
    }
    handles->push(worker);
  }

  return TEST_SUCCESS;
}

static test_return_t worker_ramp_TEARDOWN(void* object)
{
  worker_handles_st *handles= (worker_handles_st*)object;
  handles->reset();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t hostile_gearmand_SETUP(void* object)
{
  test_skip(true, has_hostile());
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  // Programmer error
  assert(hostile_server);

  set_server(hostile_server);
  worker_ramp_SETUP(object);

  return TEST_SUCCESS;
}

static test_return_t recv_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_recv_close(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t recv_corrupt_SETUP(void* object)
{
  return TEST_SKIPPED;

  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_recv_corrupt(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t resv_TEARDOWN(void* object)
{
  set_recv_close(true, 0, 0);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t send_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_send_close(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t accept_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_accept_close(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t poll_HOSTILE_POLL_CLOSED_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_poll_close(true, 4, 0, HOSTILE_POLL_CLOSED);

  return TEST_SUCCESS;
}

static test_return_t poll_HOSTILE_POLL_SHUT_WR_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_poll_close(true, 4, 0, HOSTILE_POLL_SHUT_WR);

  return TEST_SUCCESS;
}

static test_return_t poll_HOSTILE_POLL_SHUT_RD_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_poll_close(true, 4, 0, HOSTILE_POLL_SHUT_RD);

  return TEST_SUCCESS;
}

static test_return_t poll_TEARDOWN(void* object)
{
  set_poll_close(false, 0, 0, HOSTILE_POLL_CLOSED);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t send_TEARDOWN(void* object)
{
  set_send_close(true, 0, 0);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t accept_TEARDOWN(void* object)
{
  set_accept_close(true, 0, 0);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

/*********************** World functions **************************************/

static void *world_create(server_startup_st& servers, test_return_t& error)
{
  stress_server= libtest::default_port();
  if (server_startup(servers, "gearmand", stress_server, 0, NULL) == false)
  {
    error= TEST_SKIPPED;
    return NULL;
  }

  if (has_hostile())
  {
    hostile_server= libtest::get_free_port();
    if (server_startup(servers, "gearmand", hostile_server, 0, NULL) == false)
    {
      hostile_server= 0;
    }
  }

  return new worker_handles_st;
}

static bool world_destroy(void *object)
{
  worker_handles_st *handles= (worker_handles_st *)object;
  delete handles;

  return TEST_SUCCESS;
}

test_st burnin_TESTS[] ={
  {"burnin", 0, burnin_TEST },
  {0, 0, 0}
};

test_st worker_TESTS[] ={
  {"first pass", 0, worker_ramp_TEST },
  {"second pass", 0, worker_ramp_TEST },
  {"first pass 1K jobs", 0, worker_ramp_1K_TEST },
  {"first pass 10K jobs", 0, worker_ramp_10K_TEST },
  {0, 0, 0}
};

collection_st collection[] ={
  {"burnin", burnin_setup, burnin_cleanup, burnin_TESTS },
  {"plain", worker_ramp_SETUP, worker_ramp_TEARDOWN, worker_TESTS },
  {"plain against hostile server", hostile_gearmand_SETUP, worker_ramp_TEARDOWN, worker_TESTS },
  {"hostile recv()", recv_SETUP, resv_TEARDOWN, worker_TESTS },
  {"hostile recv() corrupt", recv_corrupt_SETUP, resv_TEARDOWN, worker_TESTS },
  {"hostile send()", send_SETUP, send_TEARDOWN, worker_TESTS },
  {"hostile accept()", accept_SETUP, accept_TEARDOWN, worker_TESTS },
  {"hostile poll(CLOSED)", poll_HOSTILE_POLL_CLOSED_SETUP, poll_TEARDOWN, worker_TESTS },
  {"hostile poll(SHUT_RD)", poll_HOSTILE_POLL_SHUT_RD_SETUP, poll_TEARDOWN, worker_TESTS },
  {"hostile poll(SHUT_WR)", poll_HOSTILE_POLL_SHUT_WR_SETUP, poll_TEARDOWN, worker_TESTS },
  {0, 0, 0, 0}
};

void get_world(libtest::Framework *world)
{
  world->collections(collection);
  world->create(world_create);
  world->destroy(world_destroy);
}
<commit_msg>Update Client<commit_after>/*  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
 *
 *  Gearmand client and server library.
 *
 *  Copyright (C) 2012 Data Differential, http://datadifferential.com/
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *
 *      * Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *      * Redistributions in binary form must reproduce the above
 *  copyright notice, this list of conditions and the following disclaimer
 *  in the documentation and/or other materials provided with the
 *  distribution.
 *
 *      * The names of its contributors may not be used to endorse or
 *  promote products derived from this software without specific prior
 *  written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "gear_config.h"

#include <libtest/test.hpp>

using namespace libtest;

#include <libgearman/gearman.h>

#include <libhostile/hostile.h>

#include "tests/client.h"
#include <tests/start_worker.h>
#include "tests/burnin.h"

#include "tests/workers/v2/echo_or_react.h"

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/time.h>

#define WORKER_FUNCTION_NAME "foo"

static bool has_hostile()
{
#if defined(HAVE_LIBHOSTILE) && HAVE_LIBHOSTILE
  if (1)
  {
    return true;
  }
#endif

  return false;
}

static in_port_t hostile_server= 0;
static in_port_t stress_server= 0;
static in_port_t& current_server_= stress_server;

static void reset_server()
{
  current_server_= stress_server;
}

static in_port_t current_server()
{
  return current_server_;
}

static void set_server(in_port_t& arg)
{
  current_server_= arg;
}

struct client_thread_context_st
{
  size_t count;
  size_t payload_size;

  client_thread_context_st() :
    count(0),
    payload_size(0)
  { }

  void increment()
  {
    count++;
  }
};

extern "C" {
  static __attribute__((noreturn)) void *client_thread(void *object)
  {
    client_thread_context_st *success= (client_thread_context_st *)object;
    fatal_assert(success);
    fatal_assert(success->count == 0);

    libtest::Client client(current_server());
    {
      gearman_client_set_timeout(&client, 400);
      for (size_t x= 0; x < 100; x++)
      {
        int oldstate;
        pthread_setcanceltype(PTHREAD_CANCEL_DISABLE, &oldstate);
        libtest::vchar_t payload;
        payload.resize(success->payload_size);
        gearman_return_t rc;
        void *value= gearman_client_do(&client, WORKER_FUNCTION_NAME,
                                       NULL,
                                       &payload[0],
                                       payload.size() ? random() % payload.size() : 0,
                                       NULL, &rc);
        pthread_setcanceltype(oldstate, NULL);

        if (gearman_success(rc))
        {
          success->increment();
        }

        if (value)
        {
          free(value);
        }
      }
    }

    pthread_exit(0);
  }
}

static bool join_thread(pthread_t& thread_arg, struct timespec& ts)
{
  int error;
  ts.tv_sec+= 10;

  if (HAVE_PTHREAD_TIMEDJOIN_NP)
  {
#if defined(HAVE_PTHREAD_TIMEDJOIN_NP) && HAVE_PTHREAD_TIMEDJOIN_NP
    int limit= 2;
    while (--limit)
    {
      switch ((error= pthread_timedjoin_np(thread_arg, NULL, &ts)))
      {
      case ETIMEDOUT:
        libtest::dream(1, 0);
        continue;

      case 0:
        return true;

      case ESRCH:
        return false;

      default:
        Error << "pthread_timedjoin_np() " << strerror(error);
        return false;
      }
    }

    Out << "pthread_timedjoin_np() " << strerror(error);
    if ((error= pthread_cancel(thread_arg)) != 0)
    {
      Error << "pthread_cancel() " << strerror(error);
      return false;
    }
#endif

    return true;
  }

  if ((error= pthread_join(thread_arg, NULL)) != 0)
  {
    Error << "pthread_join() " << strerror(error);
    return false;
  }

  return true;
}

static test_return_t worker_ramp_exec(const size_t payload_size)
{
  set_alarm(1200, 0);

  std::vector<pthread_t> children;
  children.resize(number_of_cpus());

  std::vector<client_thread_context_st>  success;
  success.resize(children.size());

  for (size_t x= 0; x < children.size(); x++)
  {
    success[x].payload_size= payload_size;
    pthread_create(&children[x], NULL, client_thread, &success[x]);
  }

  for (size_t x= 0; x < children.size(); x++)
  {
    struct timespec ts;
    bool join_success= false;
    int limit= 2;
    while (join_success == false and --limit)
    {
#if defined(HAVE_LIBRT) && HAVE_LIBRT
      if (HAVE_LIBRT) // This won't be called on OSX, etc,...
      {
        if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
        {
          Error << "clock_gettime(CLOCK_REALTIME) " << strerror(errno);
          continue;
        }

        join_success= join_thread(children[x], ts);
      }
      else
#endif
      {
        struct timeval tv;
        if (gettimeofday(&tv, NULL) == -1)
        {
          Error << "gettimeofday() " << strerror(errno);
          continue;
        }

        TIMEVAL_TO_TIMESPEC(&tv, &ts);
        join_success= join_thread(children[x], ts);
      }
    }

    if (join_success == false)
    {
      pthread_cancel(children[x]);
      Error << "Something went very wrong, it is likely threads were not cleaned up";
    }
  }

  return TEST_SUCCESS;
}

static test_return_t worker_ramp_TEST(void *)
{
  return worker_ramp_exec(0);
}

static test_return_t worker_ramp_1K_TEST(void *)
{
  return worker_ramp_exec(1024);
}

static test_return_t worker_ramp_10K_TEST(void *)
{
  return worker_ramp_exec(1024*10);
}

static test_return_t worker_ramp_SETUP(void *object)
{
  test_skip_valgrind();

  worker_handles_st *handles= (worker_handles_st*)object;

  gearman_function_t echo_react_fn= gearman_function_create(echo_or_react_worker_v2);
  for (uint32_t x= 0; x < 10; x++)
  {
    worker_handle_st *worker;
    if ((worker= test_worker_start(current_server(), NULL, WORKER_FUNCTION_NAME, echo_react_fn, NULL, gearman_worker_options_t())) == NULL)
    {
      return TEST_FAILURE;
    }
    handles->push(worker);
  }

  return TEST_SUCCESS;
}

static test_return_t worker_ramp_TEARDOWN(void* object)
{
  worker_handles_st *handles= (worker_handles_st*)object;
  handles->reset();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t hostile_gearmand_SETUP(void* object)
{
  test_skip(true, has_hostile());
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  // Programmer error
  assert(hostile_server);

  set_server(hostile_server);
  worker_ramp_SETUP(object);

  return TEST_SUCCESS;
}

static test_return_t recv_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_recv_close(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t recv_corrupt_SETUP(void* object)
{
  return TEST_SKIPPED;

  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_recv_corrupt(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t resv_TEARDOWN(void* object)
{
  set_recv_close(true, 0, 0);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t send_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_send_close(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t accept_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_accept_close(true, 20, 20);

  return TEST_SUCCESS;
}

static test_return_t poll_HOSTILE_POLL_CLOSED_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_poll_close(true, 4, 0, HOSTILE_POLL_CLOSED);

  return TEST_SUCCESS;
}

static test_return_t poll_HOSTILE_POLL_SHUT_WR_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_poll_close(true, 4, 0, HOSTILE_POLL_SHUT_WR);

  return TEST_SUCCESS;
}

static test_return_t poll_HOSTILE_POLL_SHUT_RD_SETUP(void* object)
{
  test_skip_valgrind();
  test_skip(true, libtest::is_massive());

  worker_ramp_SETUP(object);
  set_poll_close(true, 4, 0, HOSTILE_POLL_SHUT_RD);

  return TEST_SUCCESS;
}

static test_return_t poll_TEARDOWN(void* object)
{
  set_poll_close(false, 0, 0, HOSTILE_POLL_CLOSED);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t send_TEARDOWN(void* object)
{
  set_send_close(true, 0, 0);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

static test_return_t accept_TEARDOWN(void* object)
{
  set_accept_close(true, 0, 0);

  worker_handles_st *handles= (worker_handles_st*)object;
  handles->kill_all();

  reset_server();

  return TEST_SUCCESS;
}

/*********************** World functions **************************************/

static void *world_create(server_startup_st& servers, test_return_t& error)
{
  stress_server= libtest::default_port();
  if (server_startup(servers, "gearmand", stress_server, 0, NULL) == false)
  {
    error= TEST_SKIPPED;
    return NULL;
  }

  if (has_hostile())
  {
    hostile_server= libtest::get_free_port();
    if (server_startup(servers, "gearmand", hostile_server, 0, NULL) == false)
    {
      hostile_server= 0;
    }
  }

  return new worker_handles_st;
}

static bool world_destroy(void *object)
{
  worker_handles_st *handles= (worker_handles_st *)object;
  delete handles;

  return TEST_SUCCESS;
}

test_st burnin_TESTS[] ={
  {"burnin", 0, burnin_TEST },
  {0, 0, 0}
};

test_st worker_TESTS[] ={
  {"first pass", 0, worker_ramp_TEST },
  {"second pass", 0, worker_ramp_TEST },
  {"first pass 1K jobs", 0, worker_ramp_1K_TEST },
  {"first pass 10K jobs", 0, worker_ramp_10K_TEST },
  {0, 0, 0}
};

collection_st collection[] ={
  {"burnin", burnin_setup, burnin_cleanup, burnin_TESTS },
  {"plain", worker_ramp_SETUP, worker_ramp_TEARDOWN, worker_TESTS },
  {"plain against hostile server", hostile_gearmand_SETUP, worker_ramp_TEARDOWN, worker_TESTS },
  {"hostile recv()", recv_SETUP, resv_TEARDOWN, worker_TESTS },
  {"hostile recv() corrupt", recv_corrupt_SETUP, resv_TEARDOWN, worker_TESTS },
  {"hostile send()", send_SETUP, send_TEARDOWN, worker_TESTS },
  {"hostile accept()", accept_SETUP, accept_TEARDOWN, worker_TESTS },
  {"hostile poll(CLOSED)", poll_HOSTILE_POLL_CLOSED_SETUP, poll_TEARDOWN, worker_TESTS },
  {"hostile poll(SHUT_RD)", poll_HOSTILE_POLL_SHUT_RD_SETUP, poll_TEARDOWN, worker_TESTS },
  {"hostile poll(SHUT_WR)", poll_HOSTILE_POLL_SHUT_WR_SETUP, poll_TEARDOWN, worker_TESTS },
  {0, 0, 0, 0}
};

void get_world(libtest::Framework *world)
{
  world->collections(collection);
  world->create(world_create);
  world->destroy(world_destroy);
}
<|endoftext|>


<commit_before>/**
 * @file property.cpp
 * @author Robin Dietrich <me (at) invokr (dot) org>
 * @version 1.1
 *
 * @par License
 *    Alice Replay Parser
 *    Copyright 2014 Robin Dietrich
 *
 *    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.
 *
 */

/// Define this to test if skip length equals read length
//#define TEST_SKIPPING

#include <cmath>
#include <iostream>

#include <alice/bitstream.hpp>
#include <alice/property.hpp>

namespace dota {
    /** Reads an integer from the bitstream into the given property */
    void readInt(bitstream &stream, property* p) {
        sendprop* pr = p->getSendprop();
        const uint32_t flags = pr->getFlags();

        // Read a variable size integer
        if (flags & SPROP_ENCODED_AGAINST_TICKCOUNT) {
            if (flags & SPROP_ENCODED_AGAINST_TICKCOUNT)
                p->set(stream.nReadVarUInt32());
            else
                p->set(stream.nReadVarSInt32());

            return;
        }

        // Read a 32 bit unsigned integer
        if (flags & SPROP_UNSIGNED)
            p->set(stream.nReadUInt(pr->getBits()));
        else
            p->set(stream.nReadSInt(pr->getBits()));
    }

    /** Skips an integer in the bitstream */
    void skipInt(bitstream &stream, sendprop* p) {
        const uint32_t flags = p->getFlags();

        if (flags & SPROP_ENCODED_AGAINST_TICKCOUNT) {
            stream.nSkipVarInt();
        } else {
            stream.seekForward(p->getBits());
        }
    }

    /** Reads a float from the bitstream and returns it */
    float readFloat(bitstream &stream, property* p) {
        sendprop* pr = p->getSendprop();
        const uint32_t flags = pr->getFlags();

        // Read float coordinate
        if (flags & SPROP_COORD)
            return stream.nReadCoord();

        // Read float coordinate optimized for multiplayer games
        if (flags & SPROP_COORD_MP) {
            bool integral     = flags & SPROP_COORD_MP_INTEGRAL;
            bool lowprecision = flags & SPROP_COORD_MP_LOWPRECISION;

            return stream.nReadCoordMp(integral, lowprecision);
        }

        // Read noscale float
        if (flags & SPROP_NOSCALE) {
            // The solution below is also possible but breaks strict aliasing
            // float t = *( ( float * ) &u.v );

            // Also possible but undefined behavior
            // union { float f; uint32_t v; } u;
            // u.v = stream.read(32);
            // return u,f

            uint32_t v = stream.read(32);
            float f;
            memcpy(&f, &v, 4);
            return f;
        }

        // Read normalized float
        if (flags & SPROP_NORMAL)
            return stream.nReadNormal();

        // Read cell coordinate
        if (flags & SPROP_CELL_COORD || flags & SPROP_CELL_COORD_INTEGRAL || flags & SPROP_CELL_COORD_LOWPRECISION) {
            const bool lowprecision = flags & SPROP_CELL_COORD_LOWPRECISION;
            const bool integral     = flags & SPROP_CELL_COORD_INTEGRAL;

            return stream.nReadCellCoord(pr->getBits(), integral, lowprecision);
        }

        // Read a standard float
        const uint32_t dividend = stream.read(pr->getBits());
        const uint32_t divisor = (1 << pr->getBits()) - 1;

        const float f = ((float) dividend) / divisor;
        const float range = pr->getHighVal() - pr->getLowVal();
        return f * range + pr->getLowVal();
    }

    /** Skips a float in the bitstream */
    void skipFloat(bitstream &stream, sendprop* pr) {
        const uint32_t flags = pr->getFlags();

        // Skip float coordinate
        if (flags & SPROP_COORD) {
            stream.nSkipCoord();
            return;
        }

        // Skip float coordinate optimized for multiplayer games
        if (flags & SPROP_COORD_MP) {
            const bool integral     = flags & SPROP_COORD_MP_INTEGRAL;
            const bool lowprecision = flags & SPROP_COORD_MP_LOWPRECISION;
            stream.nSkipCoordMp(integral, lowprecision);
            return;
        }

        // Skip noscale float
        if (flags & SPROP_NOSCALE) {
            stream.seekForward(32);
            return;
        }

        // Skip normalized float
        if (flags & SPROP_NORMAL) {
            stream.nSkipNormal();
            return;
        }

        // Skip cell coordinate
        if (flags & SPROP_CELL_COORD || flags & SPROP_CELL_COORD_INTEGRAL || flags & SPROP_CELL_COORD_LOWPRECISION) {
            const bool lowprecision = flags & SPROP_CELL_COORD_LOWPRECISION;
            const bool integral     = flags & SPROP_CELL_COORD_INTEGRAL;

            stream.nSkipCellCoord(pr->getBits(), integral, lowprecision);
            return;
        }

        // Skip a standard float
        stream.seekForward(pr->getBits());
    }

    /** Reads a 3D vector from the bitstream */
    void readVector(std::array<float, 3> &vec, bitstream &stream, property* prop) {
        vec[0] = readFloat(stream, prop);
        vec[1] = readFloat(stream, prop);

        sendprop* pr = prop->getSendprop();

        if (pr->getFlags() & SPROP_NORMAL) {
            const bool sign = stream.read(1);
            const float f = vec[0] * vec[0] + vec[1] * vec[1];

            if (f < 0) {
                vec[2] = 0;
            } else {
                vec[2] = sqrt(1 - f);
            }

            if (sign)
                vec[2] *= -1;
        } else {
            vec[2] = readFloat(stream, prop);
        }
    }

    /** Skips a 3D vector */
    void skipVector(bitstream &stream, sendprop* pr) {
        skipFloat(stream, pr);
        skipFloat(stream, pr);

        if (pr->getFlags() & SPROP_NORMAL) {
            stream.seekForward(1);
        } else {
            skipFloat(stream, pr);
        }
    }

    /** Reads a 2D vector from the bitstream */
    void readVectorXY(std::array<float, 2> &vec, bitstream &stream, property* prop) {
        vec[0] = readFloat(stream, prop);
        vec[1] = readFloat(stream, prop);
    }

    /** Skips a 2D vector */
    void skipVectorXY(bitstream &stream, sendprop* pr) {
        skipFloat(stream, pr);
        skipFloat(stream, pr);
    }

    /** Reads a string from the stream, returns it's length */
    uint32_t readString(char *buf, bitstream &stream) {
        const uint32_t length = stream.read(9);

        if (length > PROPERTY_MAX_STRING_LENGTH)
            BOOST_THROW_EXCEPTION( propertyInvalidStringLength()
                << (EArgT<1, uint32_t>::info(length))
            );

        stream.readBits(buf, 8*length);
        return length;
    }

    /** Skips a string from the stream */
    void skipString(bitstream &stream) {
        const uint32_t length = stream.read(9);
        stream.nSkipString(length);
    }

    /** Reads a 64-bit integer from the stream */
    void readInt64(bitstream &stream, property* prop) {
        sendprop* pr = prop->getSendprop();

        if (pr->getFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT) {
            if (pr->getFlags() & SPROP_UNSIGNED)
                return prop->set(stream.nReadVarUInt64());
            else
                return prop->set(stream.nReadVarSInt64());
        } else {
            bool negate = false;
            std::size_t sbits = pr->getBits() - 32; // extra bits above 32

            if (!(SPROP_UNSIGNED & pr->getFlags())) {
                --sbits;
                negate = stream.read(1);
            }

            const int64_t a = stream.read(32);
            const int64_t b = stream.read(sbits);
            const int64_t val = (b << 32) | a;

            if (negate)
                prop->set(-val);
            else
                prop->set(val);
        }
    }

    /** Skips a 64 bit integer */
    void skipInt64(bitstream &stream, sendprop* pr) {
        if (pr->getFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT)
            stream.nSkipVarInt();
        else
            stream.seekForward(pr->getBits());
    }

    /** Read an array from the stream */
    void readArray(std::vector<property> &props, bitstream &stream, sendprop* prop) {
        uint32_t elements = prop->getElements();
        uint32_t bits = 0;

        // equivalent to std::floor(std::log2(elements) + 1)
        while (elements) {
            ++bits;
            elements >>= 1;
        }

        const uint32_t count = stream.read(bits);

        if (count > PROPERTY_MAX_ELEMENTS)
            BOOST_THROW_EXCEPTION( propertyInvalidNumberOfElements()
                << (EArgT<1, uint32_t>::info(count))
            );

        sendprop* aType = prop->getArrayType();
        for (uint32_t i = 0; i < count; ++i) {
            props.push_back(std::move(property::create(stream, aType)));
        }
    }

    /** Skips an array */
    void skipArray(bitstream &stream, sendprop* prop) {
        uint32_t elements = prop->getElements();
        uint32_t bits = 0;

        // equivalent to std::floor(std::log2(elements) + 1)
        while (elements) {
            ++bits;
            elements >>= 1;
        }

        const uint32_t count = stream.read(bits);

        if (count > PROPERTY_MAX_ELEMENTS)
            BOOST_THROW_EXCEPTION( propertyInvalidNumberOfElements()
                << (EArgT<1, uint32_t>::info(count))
            );

        sendprop* aType = prop->getArrayType();
        for (uint32_t i = 0; i < count; ++i) {
            property::skip(stream, aType);
        }
    }

    void property::update(bitstream &stream) {
        #ifdef TEST_SKIPPING
            uint32_t cur = stream.position();
            property::skip(stream, prop);
            uint32_t diff1 = stream.position() - cur;
            stream.seekBackward(diff1);

            // assert that seeking backwards worked and we have a clean state
            assert ( cur == stream.position() );
        #endif // TEST_SKIPPING

        switch (prop->getType()) {
            // Read Integer
            case sendprop::T_Int:
                readInt(stream, this);
                break;

            // Read Float
            case sendprop::T_Float:
                set(readFloat(stream, this));
                break;

            // Read 3D Vector
            case sendprop::T_Vector: {
                std::array<float, 3> vec;
                readVector(vec, stream, this);
                set(std::move(vec));
            } break;

            // Read 2D
            case sendprop::T_VectorXY: {
                std::array<float, 2> vec;
                readVectorXY(vec, stream, this);
                set(std::move(vec));
            } break;

            // Read String
            case sendprop::T_String: {
                char str[PROPERTY_MAX_STRING_LENGTH + 1];
                uint32_t length = readString(str, stream);
                set(std::string(str, length));
            } break;

            // Read Array
            case sendprop::T_Array:{
                std::vector<property> vec;
                readArray(vec, stream, prop);
                set(std::move(vec));
            } break;

            // Read 64 bit Integer
            case sendprop::T_Int64:
                readInt64(stream, this);
                break;

            default:
                BOOST_THROW_EXCEPTION( propertyInvalidType()
                    << (EArgT<1, uint32_t>::info(prop->getType()))
                );
                break;
        }

        #ifdef TEST_SKIPPING
            uint32_t diff2 = stream.position() - cur;
            if (diff2 != diff1) {
                std::cout << "Skip failed: " << diff1 << " (skip) " << diff2 << " (read) " << std::endl;
                std::cout << "Type ID: " << prop->getType() << std::endl;
                exit(0);
            }
        #endif // TEST_SKIPPING
    }

    property property::create(bitstream &stream, sendprop* prop) {
        property p(prop); // filled in switch
        p.update(stream);
        p.init = true;
        return std::move(p);
    }

    void property::skip(bitstream& stream, sendprop* prop) {
        switch (prop->getType()) {
            // Skip over Integer
            case sendprop::T_Int:
                skipInt(stream, prop);
                break;

            // Skip over Float
            case sendprop::T_Float:
                skipFloat(stream, prop);
                break;

            // Skip over 3D Vector
            case sendprop::T_Vector:
                skipVector(stream, prop);
                break;

            // Skip 2D Vector
            case sendprop::T_VectorXY:
                skipVectorXY(stream, prop);
                break;

            // Skip String
            case sendprop::T_String:
                skipString(stream);
                break;

            // Skip Array
            case sendprop::T_Array:
                skipArray(stream, prop);
                break;

            // Skip 64 bit Integer
            case sendprop::T_Int64:
                skipInt64(stream, prop);
                break;

            default:
                BOOST_THROW_EXCEPTION( propertyInvalidType()
                    << (EArgT<1, uint32_t>::info(prop->getType()))
                );
                break;
        }
    }
}<commit_msg>Fixed #7, Thanks @manveru<commit_after>/**
 * @file property.cpp
 * @author Robin Dietrich <me (at) invokr (dot) org>
 * @version 1.1
 *
 * @par License
 *    Alice Replay Parser
 *    Copyright 2014 Robin Dietrich
 *
 *    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.
 *
 */

/// Define this to test if skip length equals read length
//#define TEST_SKIPPING

#include <cmath>
#include <iostream>

#include <alice/bitstream.hpp>
#include <alice/property.hpp>

namespace dota {
    /** Reads an integer from the bitstream into the given property */
    void readInt(bitstream &stream, property* p) {
        sendprop* pr = p->getSendprop();
        const uint32_t flags = pr->getFlags();

        // Read a variable size integer
        if (flags & SPROP_ENCODED_AGAINST_TICKCOUNT) {
            if (flags & SPROP_UNSIGNED)
                p->set(stream.nReadVarUInt32());
            else
                p->set(stream.nReadVarSInt32());

            return;
        }

        // Read a 32 bit unsigned integer
        if (flags & SPROP_UNSIGNED)
            p->set(stream.nReadUInt(pr->getBits()));
        else
            p->set(stream.nReadSInt(pr->getBits()));
    }

    /** Skips an integer in the bitstream */
    void skipInt(bitstream &stream, sendprop* p) {
        const uint32_t flags = p->getFlags();

        if (flags & SPROP_ENCODED_AGAINST_TICKCOUNT) {
            stream.nSkipVarInt();
        } else {
            stream.seekForward(p->getBits());
        }
    }

    /** Reads a float from the bitstream and returns it */
    float readFloat(bitstream &stream, property* p) {
        sendprop* pr = p->getSendprop();
        const uint32_t flags = pr->getFlags();

        // Read float coordinate
        if (flags & SPROP_COORD)
            return stream.nReadCoord();

        // Read float coordinate optimized for multiplayer games
        if (flags & SPROP_COORD_MP) {
            bool integral     = flags & SPROP_COORD_MP_INTEGRAL;
            bool lowprecision = flags & SPROP_COORD_MP_LOWPRECISION;

            return stream.nReadCoordMp(integral, lowprecision);
        }

        // Read noscale float
        if (flags & SPROP_NOSCALE) {
            // The solution below is also possible but breaks strict aliasing
            // float t = *( ( float * ) &u.v );

            // Also possible but undefined behavior
            // union { float f; uint32_t v; } u;
            // u.v = stream.read(32);
            // return u,f

            uint32_t v = stream.read(32);
            float f;
            memcpy(&f, &v, 4);
            return f;
        }

        // Read normalized float
        if (flags & SPROP_NORMAL)
            return stream.nReadNormal();

        // Read cell coordinate
        if (flags & SPROP_CELL_COORD || flags & SPROP_CELL_COORD_INTEGRAL || flags & SPROP_CELL_COORD_LOWPRECISION) {
            const bool lowprecision = flags & SPROP_CELL_COORD_LOWPRECISION;
            const bool integral     = flags & SPROP_CELL_COORD_INTEGRAL;

            return stream.nReadCellCoord(pr->getBits(), integral, lowprecision);
        }

        // Read a standard float
        const uint32_t dividend = stream.read(pr->getBits());
        const uint32_t divisor = (1 << pr->getBits()) - 1;

        const float f = ((float) dividend) / divisor;
        const float range = pr->getHighVal() - pr->getLowVal();
        return f * range + pr->getLowVal();
    }

    /** Skips a float in the bitstream */
    void skipFloat(bitstream &stream, sendprop* pr) {
        const uint32_t flags = pr->getFlags();

        // Skip float coordinate
        if (flags & SPROP_COORD) {
            stream.nSkipCoord();
            return;
        }

        // Skip float coordinate optimized for multiplayer games
        if (flags & SPROP_COORD_MP) {
            const bool integral     = flags & SPROP_COORD_MP_INTEGRAL;
            const bool lowprecision = flags & SPROP_COORD_MP_LOWPRECISION;
            stream.nSkipCoordMp(integral, lowprecision);
            return;
        }

        // Skip noscale float
        if (flags & SPROP_NOSCALE) {
            stream.seekForward(32);
            return;
        }

        // Skip normalized float
        if (flags & SPROP_NORMAL) {
            stream.nSkipNormal();
            return;
        }

        // Skip cell coordinate
        if (flags & SPROP_CELL_COORD || flags & SPROP_CELL_COORD_INTEGRAL || flags & SPROP_CELL_COORD_LOWPRECISION) {
            const bool lowprecision = flags & SPROP_CELL_COORD_LOWPRECISION;
            const bool integral     = flags & SPROP_CELL_COORD_INTEGRAL;

            stream.nSkipCellCoord(pr->getBits(), integral, lowprecision);
            return;
        }

        // Skip a standard float
        stream.seekForward(pr->getBits());
    }

    /** Reads a 3D vector from the bitstream */
    void readVector(std::array<float, 3> &vec, bitstream &stream, property* prop) {
        vec[0] = readFloat(stream, prop);
        vec[1] = readFloat(stream, prop);

        sendprop* pr = prop->getSendprop();

        if (pr->getFlags() & SPROP_NORMAL) {
            const bool sign = stream.read(1);
            const float f = vec[0] * vec[0] + vec[1] * vec[1];

            if (f < 0) {
                vec[2] = 0;
            } else {
                vec[2] = sqrt(1 - f);
            }

            if (sign)
                vec[2] *= -1;
        } else {
            vec[2] = readFloat(stream, prop);
        }
    }

    /** Skips a 3D vector */
    void skipVector(bitstream &stream, sendprop* pr) {
        skipFloat(stream, pr);
        skipFloat(stream, pr);

        if (pr->getFlags() & SPROP_NORMAL) {
            stream.seekForward(1);
        } else {
            skipFloat(stream, pr);
        }
    }

    /** Reads a 2D vector from the bitstream */
    void readVectorXY(std::array<float, 2> &vec, bitstream &stream, property* prop) {
        vec[0] = readFloat(stream, prop);
        vec[1] = readFloat(stream, prop);
    }

    /** Skips a 2D vector */
    void skipVectorXY(bitstream &stream, sendprop* pr) {
        skipFloat(stream, pr);
        skipFloat(stream, pr);
    }

    /** Reads a string from the stream, returns it's length */
    uint32_t readString(char *buf, bitstream &stream) {
        const uint32_t length = stream.read(9);

        if (length > PROPERTY_MAX_STRING_LENGTH)
            BOOST_THROW_EXCEPTION( propertyInvalidStringLength()
                << (EArgT<1, uint32_t>::info(length))
            );

        stream.readBits(buf, 8*length);
        return length;
    }

    /** Skips a string from the stream */
    void skipString(bitstream &stream) {
        const uint32_t length = stream.read(9);
        stream.nSkipString(length);
    }

    /** Reads a 64-bit integer from the stream */
    void readInt64(bitstream &stream, property* prop) {
        sendprop* pr = prop->getSendprop();

        if (pr->getFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT) {
            if (pr->getFlags() & SPROP_UNSIGNED)
                return prop->set(stream.nReadVarUInt64());
            else
                return prop->set(stream.nReadVarSInt64());
        } else {
            bool negate = false;
            std::size_t sbits = pr->getBits() - 32; // extra bits above 32

            if (!(SPROP_UNSIGNED & pr->getFlags())) {
                --sbits;
                negate = stream.read(1);
            }

            const int64_t a = stream.read(32);
            const int64_t b = stream.read(sbits);
            const int64_t val = (b << 32) | a;

            if (negate)
                prop->set(-val);
            else
                prop->set(val);
        }
    }

    /** Skips a 64 bit integer */
    void skipInt64(bitstream &stream, sendprop* pr) {
        if (pr->getFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT)
            stream.nSkipVarInt();
        else
            stream.seekForward(pr->getBits());
    }

    /** Read an array from the stream */
    void readArray(std::vector<property> &props, bitstream &stream, sendprop* prop) {
        uint32_t elements = prop->getElements();
        uint32_t bits = 0;

        // equivalent to std::floor(std::log2(elements) + 1)
        while (elements) {
            ++bits;
            elements >>= 1;
        }

        const uint32_t count = stream.read(bits);

        if (count > PROPERTY_MAX_ELEMENTS)
            BOOST_THROW_EXCEPTION( propertyInvalidNumberOfElements()
                << (EArgT<1, uint32_t>::info(count))
            );

        sendprop* aType = prop->getArrayType();
        for (uint32_t i = 0; i < count; ++i) {
            props.push_back(std::move(property::create(stream, aType)));
        }
    }

    /** Skips an array */
    void skipArray(bitstream &stream, sendprop* prop) {
        uint32_t elements = prop->getElements();
        uint32_t bits = 0;

        // equivalent to std::floor(std::log2(elements) + 1)
        while (elements) {
            ++bits;
            elements >>= 1;
        }

        const uint32_t count = stream.read(bits);

        if (count > PROPERTY_MAX_ELEMENTS)
            BOOST_THROW_EXCEPTION( propertyInvalidNumberOfElements()
                << (EArgT<1, uint32_t>::info(count))
            );

        sendprop* aType = prop->getArrayType();
        for (uint32_t i = 0; i < count; ++i) {
            property::skip(stream, aType);
        }
    }

    void property::update(bitstream &stream) {
        #ifdef TEST_SKIPPING
            uint32_t cur = stream.position();
            property::skip(stream, prop);
            uint32_t diff1 = stream.position() - cur;
            stream.seekBackward(diff1);

            // assert that seeking backwards worked and we have a clean state
            assert ( cur == stream.position() );
        #endif // TEST_SKIPPING

        switch (prop->getType()) {
            // Read Integer
            case sendprop::T_Int:
                readInt(stream, this);
                break;

            // Read Float
            case sendprop::T_Float:
                set(readFloat(stream, this));
                break;

            // Read 3D Vector
            case sendprop::T_Vector: {
                std::array<float, 3> vec;
                readVector(vec, stream, this);
                set(std::move(vec));
            } break;

            // Read 2D
            case sendprop::T_VectorXY: {
                std::array<float, 2> vec;
                readVectorXY(vec, stream, this);
                set(std::move(vec));
            } break;

            // Read String
            case sendprop::T_String: {
                char str[PROPERTY_MAX_STRING_LENGTH + 1];
                uint32_t length = readString(str, stream);
                set(std::string(str, length));
            } break;

            // Read Array
            case sendprop::T_Array:{
                std::vector<property> vec;
                readArray(vec, stream, prop);
                set(std::move(vec));
            } break;

            // Read 64 bit Integer
            case sendprop::T_Int64:
                readInt64(stream, this);
                break;

            default:
                BOOST_THROW_EXCEPTION( propertyInvalidType()
                    << (EArgT<1, uint32_t>::info(prop->getType()))
                );
                break;
        }

        #ifdef TEST_SKIPPING
            uint32_t diff2 = stream.position() - cur;
            if (diff2 != diff1) {
                std::cout << "Skip failed: " << diff1 << " (skip) " << diff2 << " (read) " << std::endl;
                std::cout << "Type ID: " << prop->getType() << std::endl;
                exit(0);
            }
        #endif // TEST_SKIPPING
    }

    property property::create(bitstream &stream, sendprop* prop) {
        property p(prop); // filled in switch
        p.update(stream);
        p.init = true;
        return std::move(p);
    }

    void property::skip(bitstream& stream, sendprop* prop) {
        switch (prop->getType()) {
            // Skip over Integer
            case sendprop::T_Int:
                skipInt(stream, prop);
                break;

            // Skip over Float
            case sendprop::T_Float:
                skipFloat(stream, prop);
                break;

            // Skip over 3D Vector
            case sendprop::T_Vector:
                skipVector(stream, prop);
                break;

            // Skip 2D Vector
            case sendprop::T_VectorXY:
                skipVectorXY(stream, prop);
                break;

            // Skip String
            case sendprop::T_String:
                skipString(stream);
                break;

            // Skip Array
            case sendprop::T_Array:
                skipArray(stream, prop);
                break;

            // Skip 64 bit Integer
            case sendprop::T_Int64:
                skipInt64(stream, prop);
                break;

            default:
                BOOST_THROW_EXCEPTION( propertyInvalidType()
                    << (EArgT<1, uint32_t>::info(prop->getType()))
                );
                break;
        }
    }
}
<|endoftext|>


<commit_before>#define BOOST_TEST_MODULE Test_SAPList
#include <boost/test/unit_test.hpp>

/* make everything public for testing */
#define private public
#define protected public

#include "../src/SAPList.hpp"

/*
 * USER DEFINED CLASSES
 * - ActionManagerTester implements ActionManager
 * - CollisionableTester implements Collisionable
 * - 2 empty classes derived from CollisionableTester
 */

class CollisionableTester : virtual public Collisionable {
public:
    int x, y, w, h;
    virtual int getXMin() { return this->x; }
    virtual int getYMin() { return this->y; }
    virtual int getXMax() { return this->x + this->w; }
    virtual int getYMax() { return this->y + this->h; }

    CollisionableTester() : CollisionableTester(0, 0, 0, 0) {}
    CollisionableTester(int i1, int i2, int i3, int i4) :
        x(i1), y(i2), w(i3), h(i4) {}
    ~CollisionableTester() {}
};

class Collisionable_type1 : virtual public CollisionableTester {
public:
    Collisionable_type1() : CollisionableTester(0, 0, 0, 0) {};
};

class Collisionable_type2 : virtual public CollisionableTester {
public:
    Collisionable_type2() : CollisionableTester(10, 11, 12, 13) {}
};

class ActionManagerTester : virtual public ActionManager {
public:

    int status;

    /* (type1, typ2) */
    virtual void onCollision(Collisionable_type1 * o1,
                             Collisionable_type2 * o2)
    { this->status = 24; }
    virtual void onSeparation(Collisionable_type1 * o1,
                              Collisionable_type2 * o2)
    { this->status = -24; }

    /* (type1, not type2) */
    virtual void onCollision(Collisionable_type1 * o1,
                     Collisionable * o2)
    { this->status = 42; }
    virtual void onSeparation(Collisionable_type1 * o1,
                              Collisionable * o2)
    { this->status = -42; }

    /* (not type1, any type) */
    virtual void onCollision(Collisionable * o1,
                             Collisionable * o2)
    { this->status = 1; }
    virtual void onSeparation(Collisionable * o1,
                              Collisionable * o2)
    { this->status = -1; }

    ActionManagerTester() : status(0) {}
};

/*
 * FIXTURE
 * SAP fixture using previous classes
 */

struct SAPListFixture {
    ActionManagerTester am;
    SAPList cm;
    Collisionable_type1 o1;
    Collisionable_type2 o2;
    SAPListFixture () : cm(&am) {}
};

/*
 * TESTS
 */

BOOST_AUTO_TEST_SUITE( SAPList_constructors )

BOOST_FIXTURE_TEST_CASE ( Collisionable_type1_constructor, SAPListFixture ) {

    BOOST_REQUIRE_EQUAL( o1.x, 0 );
    BOOST_REQUIRE_EQUAL( o1.y, 0 );
    BOOST_REQUIRE_EQUAL( o1.w, 0 );
    BOOST_REQUIRE_EQUAL( o1.h, 0 );

}

BOOST_FIXTURE_TEST_CASE ( Collisionable_type2_constructor, SAPListFixture ) {

    BOOST_REQUIRE_EQUAL( o2.x, 10 );
    BOOST_REQUIRE_EQUAL( o2.y, 11 );
    BOOST_REQUIRE_EQUAL( o2.w, 12 );
    BOOST_REQUIRE_EQUAL( o2.h, 13 );

}

BOOST_FIXTURE_TEST_CASE( SAP_defaultContructor, SAPListFixture )
{
    BOOST_REQUIRE_EQUAL( cm.xAxis->next->prev, cm.xAxis );
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE( SAPList_functions )

BOOST_FIXTURE_TEST_CASE( SAP_mk_AABB, SAPListFixture )
{

    auto test = [](SAPList::AABB * aabb,
                   int xMin, int yMin,
                   int xMax, int yMax,
                   Collisionable * obj)
        { BOOST_CHECK_EQUAL( aabb->min[0]->value, xMin );
          BOOST_CHECK_EQUAL( aabb->min[1]->value, yMin );
          BOOST_CHECK_EQUAL( aabb->max[0]->value, xMax );
          BOOST_CHECK_EQUAL( aabb->max[1]->value, yMax );
          BOOST_CHECK_EQUAL( aabb->owner, obj); };

    SAPList::AABB * aabb1 = cm.mk_AABB(&o1);
    SAPList::AABB * aabb2 = cm.mk_AABB(&o2);

    test(aabb1, 0, 0, 0, 0, &o1);
    test(aabb2, 10, 11, 10 + 12, 11 + 13, &o2);

    delete aabb1;
    delete aabb2;

}

BOOST_FIXTURE_TEST_CASE ( SAP_swap, SAPListFixture )
{
    SAPList::EndPoint
        p1(NULL, 0, false),
        p2(NULL, 10, false),
        * null = static_cast<SAPList::EndPoint*> (NULL);

    p1.next = &p2;
    p2.prev = &p1;

    BOOST_CHECK_EQUAL ( p1.prev, null );
    BOOST_CHECK_EQUAL ( p1.next, &p2 );
    BOOST_CHECK_EQUAL ( p2.prev, &p1 );
    BOOST_CHECK_EQUAL ( p2.next, null );

    cm.swap(&p1, &p2);

    BOOST_CHECK_EQUAL( p2.prev, null );
    BOOST_CHECK_EQUAL( p2.next, &p1 );
    BOOST_CHECK_EQUAL( p1.prev, &p2 );
    BOOST_CHECK_EQUAL( p1.next, null );

}

BOOST_FIXTURE_TEST_CASE ( SAP_updateAxis, SAPListFixture )
{
    SAPList::EndPoint
        min (NULL, INT_MIN, false),
        p1 (NULL, 0, false),
        p2 (NULL, 10, false),
        p3 (NULL, 20, false),
        p4 (NULL, 30, false),
        max (NULL, INT_MAX, false);

    auto check_order = [](SAPList::EndPoint*p1,
                   SAPList::EndPoint*p2,
                   SAPList::EndPoint*p3,
                   SAPList::EndPoint*p4,
                   SAPList::EndPoint*p5,
                   SAPList::EndPoint*p6) {

        BOOST_CHECK_EQUAL( p1->next, p2 );
        BOOST_CHECK_EQUAL( p2->prev, p1 );
        BOOST_CHECK_EQUAL( p2->next, p3 );
        BOOST_CHECK_EQUAL( p3->prev, p2 );
        BOOST_CHECK_EQUAL( p3->next, p4 );
        BOOST_CHECK_EQUAL( p4->prev, p3 );
        BOOST_CHECK_EQUAL( p4->next, p5 );
        BOOST_CHECK_EQUAL( p5->prev, p4 );
        BOOST_CHECK_EQUAL( p5->next, p6 );
        BOOST_CHECK_EQUAL( p6->prev, p5 );

    };

    /* Initialization */

    min.next = &p1;
    p1.prev = &min;
    p1.next = &p2;
    p2.prev = &p1;
    p2.next = &p3;
    p3.prev = &p2;
    p3.next = &p4;
    p4.prev = &p3;
    p4.next = &max;
    max.prev = &p4;

    /* Tests */

    cm.updateAxis(&p2, &p3);
    check_order(&min, &p1, &p2, &p3, &p4, &max);

    p2.value = 25;
    p3.value = 35;
    cm.updateAxis(&p2, &p3);
    check_order(&min, &p1, &p2, &p4, &p3, &max);

    p2.value = 10;
    p3.value = 20;
    cm.updateAxis(&p2, &p3);
    check_order(&min, &p1, &p2, &p3, &p4, &max);

}

BOOST_FIXTURE_TEST_CASE( SAP_addObject, SAPListFixture )
{

    auto test = [](SAPList::EndPoint * pt,
                   Collisionable * obj, int val, bool isMin)
        { BOOST_CHECK_EQUAL(pt->owner->owner, obj);
          BOOST_CHECK_EQUAL(pt->value, val);
          BOOST_CHECK_EQUAL(pt->isMin, isMin); };

    SAPList::EndPoint

        * xMin = cm.xAxis,
        * xMax = cm.xAxis->next,

        * yMin = cm.yAxis,
        * yMax = cm.yAxis->next;

    cm.addObject(&o1);

    test(xMin->next, &o1, 0, true);
    test(xMax->prev, &o1, 0, false);
    test(yMin->next, &o1, 0, true);
    test(yMax->prev, &o1, 0, false);

    cm.addObject(&o2);

    test(xMin->next, &o1, 0, true);
    test(xMin->next->next, &o1, 0, false);
    test(xMin->next->next->next, &o2, 10, true);
    test(xMin->next->next->next->next, &o2, (10 + 12), false);

    test(xMax->prev, &o2, (10 + 12), false);
    test(xMax->prev->prev, &o2, 10, true);
    test(xMax->prev->prev->prev, &o1, 0, false);
    test(xMax->prev->prev->prev->prev, &o1, 0, true);

    test(yMin->next, &o1, 0, true);
    test(yMin->next->next, &o1, 0, false);
    test(yMin->next->next->next, &o2, 11, true);
    test(yMin->next->next->next->next, &o2, (11 + 13), false);

    test(yMax->prev, &o2, (11 + 13), false);
    test(yMax->prev->prev, &o2, 11, true);
    test(yMax->prev->prev->prev, &o1, 0, false);
    test(yMax->prev->prev->prev->prev, &o1, 0, true);

    }

BOOST_FIXTURE_TEST_CASE ( SAP_collisionCheck, SAPListFixture )
{
    SAPList::AABB
        a(0, 0, 10, 10, NULL),
        b(5, 5, 15, 15, NULL);

    BOOST_CHECK ( cm.partialCollisionCheck (a, b, 0) );
    BOOST_CHECK ( cm.partialCollisionCheck (a, b, 1) );
    BOOST_CHECK ( cm.collisionCheck (a, b) );
    BOOST_CHECK ( cm.collisionCheck (b, a) );
}

BOOST_FIXTURE_TEST_CASE( SAP_actionManager, SAPListFixture )
{

    BOOST_CHECK_EQUAL (am.status, 0); // default is zero

    cm.addObject(&o1);
    cm.addObject(&o2);

    SAPList::AABB* aabb1 = static_cast<SAPList::AABB*>(o1.boundingBox);
    aabb1->max[0]->value = 11; // o2 xMin is at 10
    aabb1->max[1]->value = 12; // o2 yMin is at 11

    cm.updateObject(&o1);

    /* FIXME: FAILURE -> generic action is called. */
    BOOST_CHECK_EQUAL (am.status, 24); // 24 on (typ1, typ2) collision

}

BOOST_AUTO_TEST_SUITE_END()
<commit_msg>Dirty implementation of dummy actionManager in order to test SAP module.<commit_after>#define BOOST_TEST_MODULE Test_SAPList
#include <boost/test/unit_test.hpp>

#include <typeinfo>

/* make everything public for testing */
#define private public
#define protected public
#include "../src/SAPList.hpp"

/*
 * USER DEFINED CLASSES
 * - ActionManagerTester implements ActionManager
 * - CollisionableTester implements Collisionable
 * - 2 empty classes derived from CollisionableTester
 */

class CollisionableTester : virtual public Collisionable {
public:
    int x, y, w, h;
    virtual int getXMin() { return this->x; }
    virtual int getYMin() { return this->y; }
    virtual int getXMax() { return this->x + this->w; }
    virtual int getYMax() { return this->y + this->h; }

    CollisionableTester() : CollisionableTester(0, 0, 0, 0) {}
    CollisionableTester(int i1, int i2, int i3, int i4) :
        x(i1), y(i2), w(i3), h(i4) {}
    ~CollisionableTester() {}
};

class Collisionable_type1 : virtual public CollisionableTester {
public:
    Collisionable_type1() : CollisionableTester(0, 0, 0, 0) {};
};

class Collisionable_type2 : virtual public CollisionableTester {
public:
    Collisionable_type2() : CollisionableTester(10, 11, 12, 13) {}
};

class ActionManagerTester : virtual public ActionManager {
public:

    int status;

    /* (type1, typ2) */
    virtual void onCollision(Collisionable_type1 * o1,
                             Collisionable_type2 * o2)
    { this->status = 24; }
    virtual void onSeparation(Collisionable_type1 * o1,
                              Collisionable_type2 * o2)
    { this->status = -24; }

    /* (type1, not type2) */
    virtual void onCollision(Collisionable_type1 * o1,
                     Collisionable * o2)
    { this->status = 42; }
    virtual void onSeparation(Collisionable_type1 * o1,
                              Collisionable * o2)
    { this->status = -42; }

    /* (not type1, any type) */
    virtual void onCollision(Collisionable * o1,
                             Collisionable * o2)
    {
        Collisionable_type1 * c1 = dynamic_cast<Collisionable_type1*>(o1);
        Collisionable_type2 * c2 = dynamic_cast<Collisionable_type2*>(o2);

        if (c1)
            if (c2)
                this->onCollision (c1, c2);
            else
                this->onCollision (c1, o2);
        else
            this->status  = 1;
    }

    virtual void onSeparation(Collisionable * o1,
                              Collisionable * o2)
    {
        Collisionable_type1 * c1 = dynamic_cast<Collisionable_type1*>(o1);
        Collisionable_type2 * c2 = dynamic_cast<Collisionable_type2*>(o2);

        if (c1)
            if (c2)
                this->onSeparation (c1, c2);
            else
                this->onSeparation (c1, o2);
        else
            this->status  = -1;
    }

    ActionManagerTester() : status(0) {}
};

/*
 * FIXTURE
 * SAP fixture using previous classes
 */

struct SAPListFixture {
    ActionManagerTester am;
    SAPList cm;
    Collisionable_type1 o1;
    Collisionable_type2 o2;
    SAPListFixture () : cm(&am) {}
};

/*
 * TESTS
 */

BOOST_AUTO_TEST_SUITE( SAPList_constructors )

BOOST_FIXTURE_TEST_CASE ( Collisionable_type1_constructor, SAPListFixture ) {

    BOOST_REQUIRE_EQUAL( o1.x, 0 );
    BOOST_REQUIRE_EQUAL( o1.y, 0 );
    BOOST_REQUIRE_EQUAL( o1.w, 0 );
    BOOST_REQUIRE_EQUAL( o1.h, 0 );

}

BOOST_FIXTURE_TEST_CASE ( Collisionable_type2_constructor, SAPListFixture ) {

    BOOST_REQUIRE_EQUAL( o2.x, 10 );
    BOOST_REQUIRE_EQUAL( o2.y, 11 );
    BOOST_REQUIRE_EQUAL( o2.w, 12 );
    BOOST_REQUIRE_EQUAL( o2.h, 13 );

}

BOOST_FIXTURE_TEST_CASE( SAP_defaultContructor, SAPListFixture )
{
    BOOST_REQUIRE_EQUAL( cm.xAxis->next->prev, cm.xAxis );
    BOOST_REQUIRE_EQUAL( cm.yAxis->next->prev, cm.yAxis );
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE( SAPList_functions )

BOOST_FIXTURE_TEST_CASE( SAP_mk_AABB, SAPListFixture )
{

    auto test = [](SAPList::AABB * aabb,
                   int xMin, int yMin,
                   int xMax, int yMax,
                   Collisionable * obj)
        { BOOST_CHECK_EQUAL( aabb->min[0]->value, xMin );
          BOOST_CHECK_EQUAL( aabb->min[1]->value, yMin );
          BOOST_CHECK_EQUAL( aabb->max[0]->value, xMax );
          BOOST_CHECK_EQUAL( aabb->max[1]->value, yMax );
          BOOST_CHECK_EQUAL( aabb->owner, obj); };

    SAPList::AABB * aabb1 = cm.mk_AABB(&o1);
    SAPList::AABB * aabb2 = cm.mk_AABB(&o2);

    test(aabb1, 0, 0, 0, 0, &o1);
    test(aabb2, 10, 11, 10 + 12, 11 + 13, &o2);

    delete aabb1;
    delete aabb2;

}

BOOST_FIXTURE_TEST_CASE ( SAP_swap, SAPListFixture )
{
    SAPList::EndPoint
        p1(NULL, 0, false),
        p2(NULL, 10, false),
        * null = static_cast<SAPList::EndPoint*> (NULL);

    p1.next = &p2;
    p2.prev = &p1;

    BOOST_CHECK_EQUAL ( p1.prev, null );
    BOOST_CHECK_EQUAL ( p1.next, &p2 );
    BOOST_CHECK_EQUAL ( p2.prev, &p1 );
    BOOST_CHECK_EQUAL ( p2.next, null );

    cm.swap(&p1, &p2);

    BOOST_CHECK_EQUAL( p2.prev, null );
    BOOST_CHECK_EQUAL( p2.next, &p1 );
    BOOST_CHECK_EQUAL( p1.prev, &p2 );
    BOOST_CHECK_EQUAL( p1.next, null );

}

BOOST_FIXTURE_TEST_CASE ( SAP_updateAxis, SAPListFixture )
{
    SAPList::EndPoint
        min (NULL, INT_MIN, false),
        p1 (NULL, 0, false),
        p2 (NULL, 10, false),
        p3 (NULL, 20, false),
        p4 (NULL, 30, false),
        max (NULL, INT_MAX, false);

    auto check_order = [](SAPList::EndPoint*p1,
                   SAPList::EndPoint*p2,
                   SAPList::EndPoint*p3,
                   SAPList::EndPoint*p4,
                   SAPList::EndPoint*p5,
                   SAPList::EndPoint*p6) {

        BOOST_CHECK_EQUAL( p1->next, p2 );
        BOOST_CHECK_EQUAL( p2->prev, p1 );
        BOOST_CHECK_EQUAL( p2->next, p3 );
        BOOST_CHECK_EQUAL( p3->prev, p2 );
        BOOST_CHECK_EQUAL( p3->next, p4 );
        BOOST_CHECK_EQUAL( p4->prev, p3 );
        BOOST_CHECK_EQUAL( p4->next, p5 );
        BOOST_CHECK_EQUAL( p5->prev, p4 );
        BOOST_CHECK_EQUAL( p5->next, p6 );
        BOOST_CHECK_EQUAL( p6->prev, p5 );

    };

    /* Initialization */

    min.next = &p1;
    p1.prev = &min;
    p1.next = &p2;
    p2.prev = &p1;
    p2.next = &p3;
    p3.prev = &p2;
    p3.next = &p4;
    p4.prev = &p3;
    p4.next = &max;
    max.prev = &p4;

    /* Tests */

    cm.updateAxis(&p2, &p3);
    check_order(&min, &p1, &p2, &p3, &p4, &max);

    p2.value = 25;
    p3.value = 35;
    cm.updateAxis(&p2, &p3);
    check_order(&min, &p1, &p2, &p4, &p3, &max);

    p2.value = 10;
    p3.value = 20;
    cm.updateAxis(&p2, &p3);
    check_order(&min, &p1, &p2, &p3, &p4, &max);

}

BOOST_FIXTURE_TEST_CASE( SAP_addObject, SAPListFixture )
{

    auto test = [](SAPList::EndPoint * pt,
                   Collisionable * obj, int val, bool isMin)
        { BOOST_CHECK_EQUAL(pt->owner->owner, obj);
          BOOST_CHECK_EQUAL(pt->value, val);
          BOOST_CHECK_EQUAL(pt->isMin, isMin); };

    SAPList::EndPoint

        * xMin = cm.xAxis,
        * xMax = cm.xAxis->next,

        * yMin = cm.yAxis,
        * yMax = cm.yAxis->next;

    cm.addObject(&o1);

    test(xMin->next, &o1, 0, true);
    test(xMax->prev, &o1, 0, false);
    test(yMin->next, &o1, 0, true);
    test(yMax->prev, &o1, 0, false);

    cm.addObject(&o2);

    test(xMin->next, &o1, 0, true);
    test(xMin->next->next, &o1, 0, false);
    test(xMin->next->next->next, &o2, 10, true);
    test(xMin->next->next->next->next, &o2, (10 + 12), false);

    test(xMax->prev, &o2, (10 + 12), false);
    test(xMax->prev->prev, &o2, 10, true);
    test(xMax->prev->prev->prev, &o1, 0, false);
    test(xMax->prev->prev->prev->prev, &o1, 0, true);

    test(yMin->next, &o1, 0, true);
    test(yMin->next->next, &o1, 0, false);
    test(yMin->next->next->next, &o2, 11, true);
    test(yMin->next->next->next->next, &o2, (11 + 13), false);

    test(yMax->prev, &o2, (11 + 13), false);
    test(yMax->prev->prev, &o2, 11, true);
    test(yMax->prev->prev->prev, &o1, 0, false);
    test(yMax->prev->prev->prev->prev, &o1, 0, true);

    }

BOOST_FIXTURE_TEST_CASE ( SAP_collisionCheck, SAPListFixture )
{
    SAPList::AABB
        a(0, 0, 10, 10, NULL),
        b(5, 5, 15, 15, NULL);

    BOOST_CHECK ( cm.partialCollisionCheck (a, b, 0) );
    BOOST_CHECK ( cm.partialCollisionCheck (a, b, 1) );
    BOOST_CHECK ( cm.collisionCheck (a, b) );
    BOOST_CHECK ( cm.collisionCheck (b, a) );
}

BOOST_FIXTURE_TEST_CASE( SAP_actionManager, SAPListFixture )
{

    BOOST_CHECK_EQUAL (am.status, 0); // default is zero

    cm.addObject(&o1);
    cm.addObject(&o2);

    SAPList::AABB* aabb = static_cast<SAPList::AABB*>(o1.boundingBox);
    aabb->max[0]->value = 11; // o2 xMin is at 10
    aabb->max[1]->value = 12; // o2 yMin is at 11
    cm.updateObject(&o1);
    BOOST_CHECK_EQUAL (am.status, 24); // 24 on (typ1, typ2) collision

    aabb->max[0]->value = 0; // o2 xMin is at 10
    aabb->max[1]->value = 0; // o2 yMin is at 11
    cm.updateObject(&o1);
    BOOST_CHECK_EQUAL (am.status, -24); // -24 on (typ1, typ2) separation

    Collisionable_type1 tmp;
    cm.addObject(&tmp);
    aabb = static_cast<SAPList::AABB*>(tmp.boundingBox);

    aabb->max[0]->value = 2;
    aabb->max[1]->value = 2;
    cm.updateObject(&tmp);
    BOOST_CHECK_EQUAL (am.status, 42); // 42 on (typ1, typ1) collision

    aabb->min[0]->value = 1;
    aabb->min[1]->value = 1;
    cm.updateObject(&tmp);
    BOOST_CHECK_EQUAL (am.status, -42); // -42 on (typ1, typ1) separation

}

BOOST_AUTO_TEST_SUITE_END()
<|endoftext|>


<commit_before>//===--- utils/unittest/UnitTestMain/TestMain.cpp - unittest driver -------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/Config/config.h"
#include "llvm/Support/Signals.h"
#include "gtest/gtest.h"

#if defined(LLVM_ON_WIN32)
# include <windows.h>
# if defined(_MSC_VER)
#   include <crtdbg.h>
# endif
#endif

int main(int argc, char **argv) {
  llvm::sys::PrintStackTraceOnErrorSignal();
  testing::InitGoogleTest(&argc, argv);

# if defined(LLVM_ON_WIN32)
  // Disable all of the possible ways Windows conspires to make automated
  // testing impossible.
  ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#   if defined(_MSC_VER)
    ::_set_error_mode(_OUT_TO_STDERR);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
#   endif
# endif

  return RUN_ALL_TESTS();
}
<commit_msg>Add basic command line parsing to TestMain.<commit_after>//===--- utils/unittest/UnitTestMain/TestMain.cpp - unittest driver -------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/Config/config.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Signals.h"
#include "gtest/gtest.h"

#if defined(LLVM_ON_WIN32)
# include <windows.h>
# if defined(_MSC_VER)
#   include <crtdbg.h>
# endif
#endif

int main(int argc, char **argv) {
  llvm::sys::PrintStackTraceOnErrorSignal();
  testing::InitGoogleTest(&argc, argv);
  llvm::cl::ParseCommandLineOptions(argc, argv);

# if defined(LLVM_ON_WIN32)
  // Disable all of the possible ways Windows conspires to make automated
  // testing impossible.
  ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#   if defined(_MSC_VER)
    ::_set_error_mode(_OUT_TO_STDERR);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
#   endif
# endif

  return RUN_ALL_TESTS();
}
<|endoftext|>


<commit_before>//===- llvm/unittest/ADT/PointerUnionTest.cpp - Optional unit tests -------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"
#include "llvm/ADT/PointerUnion.h"
using namespace llvm;

namespace {

typedef PointerUnion<int *, float *> PU;

struct PointerUnionTest : public testing::Test {
  float f;
  int i;

  PU a, b, n;

  PointerUnionTest() : f(3.14f), i(42), a(&f), b(&i), n() {}
};

TEST_F(PointerUnionTest, Comparison) {
  EXPECT_TRUE(a != b);
  EXPECT_FALSE(a == b);
  EXPECT_TRUE(b != n);
  EXPECT_FALSE(b == n);
}

TEST_F(PointerUnionTest, Null) {
  EXPECT_FALSE(a.isNull());
  EXPECT_FALSE(b.isNull());
  EXPECT_TRUE(n.isNull());
  EXPECT_FALSE(!a);
  EXPECT_FALSE(!b);
  EXPECT_TRUE(!n);
  // workaround an issue with EXPECT macros and explicit bool
  EXPECT_TRUE((bool)a);
  EXPECT_TRUE((bool)b);
  EXPECT_FALSE(n);
}

TEST_F(PointerUnionTest, Is) {
  EXPECT_FALSE(a.is<int *>());
  EXPECT_TRUE(a.is<float *>());
  EXPECT_TRUE(b.is<int *>());
  EXPECT_FALSE(b.is<float *>());
  EXPECT_TRUE(n.is<int *>());
  EXPECT_FALSE(n.is<float *>());
}

TEST_F(PointerUnionTest, Get) {
  EXPECT_EQ(a.get<float *>(), &f);
  EXPECT_EQ(b.get<int *>(), &i);
  EXPECT_EQ(n.get<int *>(), (int *)0);
}

} // end anonymous namespace
<commit_msg>[cleanup] Add some actual positive tests for equality. This unittest never actually compared for equality two pointer unions that were equal. Fortunately, things seem to work. =]<commit_after>//===- llvm/unittest/ADT/PointerUnionTest.cpp - Optional unit tests -------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"
#include "llvm/ADT/PointerUnion.h"
using namespace llvm;

namespace {

typedef PointerUnion<int *, float *> PU;

struct PointerUnionTest : public testing::Test {
  float f;
  int i;

  PU a, b, c, n;

  PointerUnionTest() : f(3.14f), i(42), a(&f), b(&i), c(&i), n() {}
};

TEST_F(PointerUnionTest, Comparison) {
  EXPECT_TRUE(a == a);
  EXPECT_FALSE(a != a);
  EXPECT_TRUE(a != b);
  EXPECT_FALSE(a == b);
  EXPECT_TRUE(b == c);
  EXPECT_FALSE(b != c);
  EXPECT_TRUE(b != n);
  EXPECT_FALSE(b == n);
}

TEST_F(PointerUnionTest, Null) {
  EXPECT_FALSE(a.isNull());
  EXPECT_FALSE(b.isNull());
  EXPECT_TRUE(n.isNull());
  EXPECT_FALSE(!a);
  EXPECT_FALSE(!b);
  EXPECT_TRUE(!n);
  // workaround an issue with EXPECT macros and explicit bool
  EXPECT_TRUE((bool)a);
  EXPECT_TRUE((bool)b);
  EXPECT_FALSE(n);
}

TEST_F(PointerUnionTest, Is) {
  EXPECT_FALSE(a.is<int *>());
  EXPECT_TRUE(a.is<float *>());
  EXPECT_TRUE(b.is<int *>());
  EXPECT_FALSE(b.is<float *>());
  EXPECT_TRUE(n.is<int *>());
  EXPECT_FALSE(n.is<float *>());
}

TEST_F(PointerUnionTest, Get) {
  EXPECT_EQ(a.get<float *>(), &f);
  EXPECT_EQ(b.get<int *>(), &i);
  EXPECT_EQ(n.get<int *>(), (int *)0);
}

} // end anonymous namespace
<|endoftext|>


<commit_before>//===-- JSONExprTests.cpp - JSON expression unit tests ----------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "JSONExpr.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
namespace json {
namespace {

std::string s(const Expr &E) { return llvm::formatv("{0}", E).str(); }
std::string sp(const Expr &E) { return llvm::formatv("{0:2}", E).str(); }

TEST(JSONExprTests, Types) {
  EXPECT_EQ("true", s(true));
  EXPECT_EQ("null", s(nullptr));
  EXPECT_EQ("2.5", s(2.5));
  EXPECT_EQ(R"("foo")", s("foo"));
  EXPECT_EQ("[1,2,3]", s({1, 2, 3}));
  EXPECT_EQ(R"({"x":10,"y":20})", s(obj{{"x", 10}, {"y", 20}}));
}

TEST(JSONExprTests, Constructors) {
  // Lots of edge cases around empty and singleton init lists.
  EXPECT_EQ("[[[3]]]", s({{{3}}}));
  EXPECT_EQ("[[[]]]", s({{{}}}));
  EXPECT_EQ("[[{}]]", s({{obj{}}}));
  EXPECT_EQ(R"({"A":{"B":{}}})", s(obj{{"A", obj{{"B", obj{}}}}}));
  EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})",
            s(obj{{"A", obj{{"B", obj{{"X", "Y"}}}}}}));
}

TEST(JSONExprTests, StringOwnership) {
  char X[] = "Hello";
  Expr Alias = static_cast<const char *>(X);
  X[1] = 'a';
  EXPECT_EQ(R"("Hallo")", s(Alias));

  std::string Y = "Hello";
  Expr Copy = Y;
  Y[1] = 'a';
  EXPECT_EQ(R"("Hello")", s(Copy));
}

TEST(JSONExprTests, CanonicalOutput) {
  // Objects are sorted (but arrays aren't)!
  EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(obj{{"a", 1}, {"c", 3}, {"b", 2}}));
  EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"}));
  EXPECT_EQ("3", s(3.0));
}

TEST(JSONExprTests, Escaping) {
  std::string test = {
      0,                    // Strings may contain nulls.
      '\b',   '\f',         // Have mnemonics, but we escape numerically.
      '\r',   '\n',   '\t', // Escaped with mnemonics.
      'S',    '\"',   '\\', // Printable ASCII characters.
      '\x7f',               // Delete is not escaped.
      '\xce', '\x94',       // Non-ASCII UTF-8 is not escaped.
  };
  EXPECT_EQ(R"("\u0000\u0008\u000c\r\n\tS\"\\)"
            u8"\x7f\xce\x94\"",
            s(test));

  EXPECT_EQ(R"({"object keys are\nescaped":true})",
            s(obj{{"object keys are\nescaped", true}}));
}

TEST(JSONExprTests, PrettyPrinting) {
  const char str[] = R"({
  "empty_array": [],
  "empty_object": {},
  "full_array": [
    1,
    null
  ],
  "full_object": {
    "nested_array": [
      {
        "property": "value"
      }
    ]
  }
})";

  EXPECT_EQ(
      str,
      sp(obj{
          {"empty_object", obj{}},
          {"empty_array", {}},
          {"full_array", {1, nullptr}},
          {"full_object",
           obj{
               {"nested_array",
                {obj{
                    {"property", "value"},
                }}},
           }},
      }));
}

} // namespace
} // namespace json
} // namespace clangd
} // namespace clang
<commit_msg>[clangd] MSVC - third time's the charm<commit_after>//===-- JSONExprTests.cpp - JSON expression unit tests ----------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "JSONExpr.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
namespace json {
namespace {

std::string s(const Expr &E) { return llvm::formatv("{0}", E).str(); }
std::string sp(const Expr &E) { return llvm::formatv("{0:2}", E).str(); }

TEST(JSONExprTests, Types) {
  EXPECT_EQ("true", s(true));
  EXPECT_EQ("null", s(nullptr));
  EXPECT_EQ("2.5", s(2.5));
  EXPECT_EQ(R"("foo")", s("foo"));
  EXPECT_EQ("[1,2,3]", s({1, 2, 3}));
  EXPECT_EQ(R"({"x":10,"y":20})", s(obj{{"x", 10}, {"y", 20}}));
}

TEST(JSONExprTests, Constructors) {
  // Lots of edge cases around empty and singleton init lists.
  EXPECT_EQ("[[[3]]]", s({{{3}}}));
  EXPECT_EQ("[[[]]]", s({{{}}}));
  EXPECT_EQ("[[{}]]", s({{obj{}}}));
  EXPECT_EQ(R"({"A":{"B":{}}})", s(obj{{"A", obj{{"B", obj{}}}}}));
  EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})",
            s(obj{{"A", obj{{"B", obj{{"X", "Y"}}}}}}));
}

TEST(JSONExprTests, StringOwnership) {
  char X[] = "Hello";
  Expr Alias = static_cast<const char *>(X);
  X[1] = 'a';
  EXPECT_EQ(R"("Hallo")", s(Alias));

  std::string Y = "Hello";
  Expr Copy = Y;
  Y[1] = 'a';
  EXPECT_EQ(R"("Hello")", s(Copy));
}

TEST(JSONExprTests, CanonicalOutput) {
  // Objects are sorted (but arrays aren't)!
  EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(obj{{"a", 1}, {"c", 3}, {"b", 2}}));
  EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"}));
  EXPECT_EQ("3", s(3.0));
}

TEST(JSONExprTests, Escaping) {
  std::string test = {
      0,                    // Strings may contain nulls.
      '\b',   '\f',         // Have mnemonics, but we escape numerically.
      '\r',   '\n',   '\t', // Escaped with mnemonics.
      'S',    '\"',   '\\', // Printable ASCII characters.
      '\x7f',               // Delete is not escaped.
      '\xce', '\x94',       // Non-ASCII UTF-8 is not escaped.
  };
  EXPECT_EQ(R"("\u0000\u0008\u000c\r\n\tS\"\\)"
            "\x7f\xCE\x94\"",
            s(test));

  EXPECT_EQ(R"({"object keys are\nescaped":true})",
            s(obj{{"object keys are\nescaped", true}}));
}

TEST(JSONExprTests, PrettyPrinting) {
  const char str[] = R"({
  "empty_array": [],
  "empty_object": {},
  "full_array": [
    1,
    null
  ],
  "full_object": {
    "nested_array": [
      {
        "property": "value"
      }
    ]
  }
})";

  EXPECT_EQ(
      str,
      sp(obj{
          {"empty_object", obj{}},
          {"empty_array", {}},
          {"full_array", {1, nullptr}},
          {"full_object",
           obj{
               {"nested_array",
                {obj{
                    {"property", "value"},
                }}},
           }},
      }));
}

} // namespace
} // namespace json
} // namespace clangd
} // namespace clang
<|endoftext|>


<commit_before>///////////////////////////////////////////////////////////////////////////////
// This source file is part of Hect.
//
// Copyright (c) 2014 Colin Hill
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
///////////////////////////////////////////////////////////////////////////////
#include "Socket.h"

#include <cstring>
#include <enet/enet.h>

#include "Hect/Core/Error.h"
#include "Hect/Core/Format.h"
#include "Hect/Core/Logging.h"

using namespace hect;

Socket::Socket()
{
    initializeENet();
}

Socket::Socket(size_t peerCount, size_t channelCount) :
    _peerCount(peerCount),
    _channelCount(channelCount)
{
    initializeENet();
}

Socket::~Socket()
{
    // Destroy the associated host if there is one
    if (_enetHost)
    {
        enet_host_destroy(_enetHost);
        _enetHost = nullptr;
    }

    deinitializeENet();
}

void Socket::listenOnPort(Port port)
{
    // Ensure that the socket is not already listening
    if (_listening)
    {
        throw Error("Socket is already listening on a port");
    }
    _listening = true;

    // Ensure that the socket has not attempted to connect to a remote socket
    if (_enetHost)
    {
        throw Error("Socket has active connections and cannot listen on a port");
    }

    // Create the ENet address to listen from
    ENetAddress address;
    address.host = ENET_HOST_ANY;
    address.port = port;

    // Create the ENet host
    _enetHost = enet_host_create(&address, _peerCount, _channelCount, 0, 0);
    if (!_enetHost)
    {
        throw Error("Failed to create ENet host");
    }

    HECT_INFO(format("Listening for connections on port %i", port));
}

Peer Socket::requestConnectTo(IPAddress address, Port port)
{
    // Create the ENet host (non-listening) if a host was not already created
    if (!_enetHost)
    {
        _enetHost = enet_host_create(nullptr, _peerCount, _channelCount, 0, 0);
        if (!_enetHost)
        {
            throw Error("Failed to create ENet host");
        }
    }

    // Create the ENet address to connect to
    ENetAddress enetAddress;
    enetAddress.host = static_cast<uint32_t>(address);
    enetAddress.port = port;

    // Perform the connection
    ENetPeer* enetPeer = enet_host_connect(_enetHost, &enetAddress, _enetHost->channelLimit, 0);
    if (!enetPeer)
    {
        throw Error("Failed to create ENet peer");
    }

    HECT_INFO(format("Requested connection to peer at address %s on port %d", address.asString().c_str(), port));

    // Return the associated peer
    return Peer(enetPeer);
}

void Socket::requestDisconnectFrom(Peer peer)
{
    // Trigger the disconnect
    if (peer.state() == PeerState_Connected)
    {
        enet_peer_disconnect(peer._enetPeer, 0);
        HECT_INFO(format("Requested disconnection from peer at address %s", peer.address().asString().c_str()));
    }
    else
    {
        enet_peer_reset(peer._enetPeer);
    }
}

bool Socket::pollEvent(SocketEvent& event, TimeSpan timeOut)
{
    // Poll for the ENet event
    ENetEvent enetEvent;
    if (enet_host_service(_enetHost, &enetEvent, static_cast<uint32_t>(timeOut.milliseconds())) > 0)
    {
        Peer peer(enetEvent.peer);

        // Create the event
        event = SocketEvent();
        event.type = static_cast<SocketEventType>(enetEvent.type);
        event.peer = peer;

        // If it is a packet receive event then copy the packet data to the
        // packet of the event
        if (enetEvent.type == ENET_EVENT_TYPE_RECEIVE)
        {
            ByteVector data(enetEvent.packet->dataLength, 0);
            std::memcpy(&data[0], enetEvent.packet->data, data.size());
            event.packet = Packet(data, PacketFlag_None);
            enet_packet_destroy(enetEvent.packet);
        }

        // Log connection/disconnection events
        PeerId id = peer.id();
        const char* address = peer.address().asString().c_str();
        switch (event.type)
        {
        case SocketEventType_Connect:
            HECT_INFO(format("Connected to remote socket at address %s (peer id = %d)", address, id));
            break;
        case SocketEventType_Disconnect:
            HECT_INFO(format("Disconnected from remote socket at address %s (peer id = %d)", address, id));
            break;
        default:
            break;
        }

        // An event was received
        return true;
    }

    // An event was not received
    return false;
}

void Socket::sendPacket(Peer peer, Channel channel, const Packet& packet)
{
    const ByteVector& data = packet.data();
    ENetPacket* enetPacket = enet_packet_create(&data[0], data.size(), packet.flags());
    enet_peer_send(peer._enetPeer, channel, enetPacket);
}

void Socket::broadcastPacket(Channel channel, const Packet& packet)
{
    const ByteVector& data = packet.data();
    ENetPacket* enetPacket = enet_packet_create(&data[0], data.size(), packet.flags());
    enet_host_broadcast(_enetHost, channel, enetPacket);
}

void Socket::flush()
{
    enet_host_flush(_enetHost);
}

void Socket::initializeENet()
{
    std::lock_guard<std::mutex> lock(enetInitializationMutex);

    // If this is the first call to initialize ENet
    if (enetInitializationCounter == 0)
    {
        ++enetInitializationCounter;

        // Intialize ENet
        if (enet_initialize() != 0)
        {
            throw Error("Failed to initialized ENet");
        }

        // Log the ENet version
        ENetVersion version = ENET_VERSION;
        HECT_INFO(format("ENet version %d.%d.%d", ENET_VERSION_GET_MAJOR(version), ENET_VERSION_GET_MINOR(version), ENET_VERSION_GET_PATCH(version)));
    }
    else
    {
        // Already initalized
        ++enetInitializationCounter;
    }
}

void Socket::deinitializeENet()
{
    std::lock_guard<std::mutex> lock(enetInitializationMutex);

    if (enetInitializationCounter == 1)
    {
        --enetInitializationCounter;
        enet_deinitialize();
    }
    else
    {
        --enetInitializationCounter;
    }
}

int Socket::enetInitializationCounter = 0;
std::mutex Socket::enetInitializationMutex;<commit_msg>Fixed memory errors recently added to Socket<commit_after>///////////////////////////////////////////////////////////////////////////////
// This source file is part of Hect.
//
// Copyright (c) 2014 Colin Hill
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
///////////////////////////////////////////////////////////////////////////////
#include "Socket.h"

#include <cstring>
#include <enet/enet.h>

#include "Hect/Core/Error.h"
#include "Hect/Core/Format.h"
#include "Hect/Core/Logging.h"

using namespace hect;

Socket::Socket()
{
    initializeENet();
}

Socket::Socket(size_t peerCount, size_t channelCount) :
    _peerCount(peerCount),
    _channelCount(channelCount)
{
    initializeENet();
}

Socket::~Socket()
{
    // Destroy the associated host if there is one
    if (_enetHost)
    {
        enet_host_destroy(_enetHost);
        _enetHost = nullptr;
    }

    deinitializeENet();
}

void Socket::listenOnPort(Port port)
{
    // Ensure that the socket is not already listening
    if (_listening)
    {
        throw Error("Socket is already listening on a port");
    }
    _listening = true;

    // Ensure that the socket has not attempted to connect to a remote socket
    if (_enetHost)
    {
        throw Error("Socket has active connections and cannot listen on a port");
    }

    // Create the ENet address to listen from
    ENetAddress address;
    address.host = ENET_HOST_ANY;
    address.port = port;

    // Create the ENet host
    _enetHost = enet_host_create(&address, _peerCount, _channelCount, 0, 0);
    if (!_enetHost)
    {
        throw Error("Failed to create ENet host");
    }

    HECT_INFO(format("Listening for connections on port %i", port));
}

Peer Socket::requestConnectTo(IPAddress address, Port port)
{
    // Create the ENet host (non-listening) if a host was not already created
    if (!_enetHost)
    {
        _enetHost = enet_host_create(nullptr, _peerCount, _channelCount, 0, 0);
        if (!_enetHost)
        {
            throw Error("Failed to create ENet host");
        }
    }

    // Create the ENet address to connect to
    ENetAddress enetAddress;
    enetAddress.host = static_cast<uint32_t>(address);
    enetAddress.port = port;

    // Perform the connection
    ENetPeer* enetPeer = enet_host_connect(_enetHost, &enetAddress, _enetHost->channelLimit, 0);
    if (!enetPeer)
    {
        throw Error("Failed to create ENet peer");
    }

    HECT_INFO(format("Requested connection to peer at address %s on port %d", address.asString().c_str(), port));

    // Return the associated peer
    return Peer(enetPeer);
}

void Socket::requestDisconnectFrom(Peer peer)
{
    // Trigger the disconnect
    if (peer.state() == PeerState_Connected)
    {
        enet_peer_disconnect(peer._enetPeer, 0);
        HECT_INFO(format("Requested disconnection from peer at address %s", peer.address().asString().c_str()));
    }
    else
    {
        enet_peer_reset(peer._enetPeer);
    }
}

bool Socket::pollEvent(SocketEvent& event, TimeSpan timeOut)
{
    // Poll for the ENet event
    ENetEvent enetEvent;
    if (enet_host_service(_enetHost, &enetEvent, static_cast<uint32_t>(timeOut.milliseconds())) > 0)
    {
        Peer peer(enetEvent.peer);

        // Create the event
        event = SocketEvent();
        event.type = static_cast<SocketEventType>(enetEvent.type);
        event.peer = peer;

        // If it is a packet receive event then copy the packet data to the
        // packet of the event
        if (enetEvent.type == ENET_EVENT_TYPE_RECEIVE)
        {
            ByteVector data(enetEvent.packet->dataLength, 0);
            std::memcpy(&data[0], enetEvent.packet->data, data.size());
            event.packet = Packet(data, PacketFlag_None);
            enet_packet_destroy(enetEvent.packet);
        }

        // Log connection/disconnection events
        switch (event.type)
        {
        case SocketEventType_Connect:
            HECT_INFO(format("Connected to remote socket at address %s (peer id = %d)", peer.address().asString().c_str(), peer.id()));
            break;
        case SocketEventType_Disconnect:
            HECT_INFO(format("Disconnected from remote socket at address %s (peer id = %d)", peer.address().asString().c_str(), peer.id()));
            break;
        default:
            break;
        }

        // An event was received
        return true;
    }

    // An event was not received
    return false;
}

void Socket::sendPacket(Peer peer, Channel channel, const Packet& packet)
{
    const ByteVector& data = packet.data();
    ENetPacket* enetPacket = enet_packet_create(&data[0], data.size(), packet.flags());
    enet_peer_send(peer._enetPeer, channel, enetPacket);
}

void Socket::broadcastPacket(Channel channel, const Packet& packet)
{
    const ByteVector& data = packet.data();
    ENetPacket* enetPacket = enet_packet_create(&data[0], data.size(), packet.flags());
    enet_host_broadcast(_enetHost, channel, enetPacket);
}

void Socket::flush()
{
    enet_host_flush(_enetHost);
}

void Socket::initializeENet()
{
    std::lock_guard<std::mutex> lock(enetInitializationMutex);

    // If this is the first call to initialize ENet
    if (enetInitializationCounter == 0)
    {
        ++enetInitializationCounter;

        // Intialize ENet
        if (enet_initialize() != 0)
        {
            throw Error("Failed to initialized ENet");
        }

        // Log the ENet version
        ENetVersion version = ENET_VERSION;
        HECT_INFO(format("ENet version %d.%d.%d", ENET_VERSION_GET_MAJOR(version), ENET_VERSION_GET_MINOR(version), ENET_VERSION_GET_PATCH(version)));
    }
    else
    {
        // Already initalized
        ++enetInitializationCounter;
    }
}

void Socket::deinitializeENet()
{
    std::lock_guard<std::mutex> lock(enetInitializationMutex);

    if (enetInitializationCounter == 1)
    {
        --enetInitializationCounter;
        enet_deinitialize();
    }
    else
    {
        --enetInitializationCounter;
    }
}

int Socket::enetInitializationCounter = 0;
std::mutex Socket::enetInitializationMutex;<|endoftext|>