Skip to content

Commit

Permalink
correct thread model for unit test timeouts
Browse files Browse the repository at this point in the history
it seems that child threads can't terminate the whole process,
so we need to instead use a worker thread and make the
main thread the timer / return thread.
  • Loading branch information
cbeck88 committed Apr 8, 2014
1 parent e5b3951 commit 58de721
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 22 deletions.
62 changes: 59 additions & 3 deletions src/game.cpp
Expand Up @@ -48,6 +48,10 @@
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/filter/gzip.hpp>

#include <SDL.h>
#include <SDL_thread.h>


#ifdef HAVE_VISUAL_LEAK_DETECTOR
#include "vld.h"
#endif
Expand Down Expand Up @@ -406,6 +410,26 @@ static void init_locale() {
textdomain (PACKAGE);
}

static SDL_sem * worker_sem;
/**
* Function used by worker thread to perform unit test with timeout.
*/
static int run_unit_test (void * data){
std::pair<game_controller*, int*> * mydata = (std::pair<game_controller*, int*> *) data;
if (SDL_SemWait(worker_sem) == -1) {
std::cerr << "Worker failed to lock worker semaphore!" << std::endl;
}
game_controller * game = mydata->first;
int * return_value = mydata->second;
int ret_val = game->unit_test();
*return_value = ret_val;

if (SDL_SemPost(worker_sem) == -1) {
std::cerr << "Worker failed to unlock worker semaphore after working!" << std::endl;
}
return ret_val;
}

/**
* Setups the game environment and enters
* the titlescreen or game loops.
Expand Down Expand Up @@ -522,9 +546,41 @@ static int do_gameloop(int argc, char** argv)
loadscreen_manager.reset();

if(cmdline_opts.unit_test) {
int worker_result = game->unit_test();
std::cout << ((worker_result == 0) ? "PASS TEST: " : "FAIL TEST: ") << *cmdline_opts.unit_test << std::endl;
return worker_result;
if(cmdline_opts.timeout && *cmdline_opts.timeout > 0) {
int worker_result = 2; //Default timeout return value if worker fails to return
worker_sem = SDL_CreateSemaphore(1);
if (worker_sem == NULL) {
std::cerr << "Failed to create a semaphore for timeout worker thread!" << std::endl;
std::cout << "FAIL TEST (TIMEOUT): " << *cmdline_opts.unit_test << std::endl;
return 2;
}

std::pair<game_controller *, int *> data(&(*game), &worker_result);
SDL_Thread *worker = SDL_CreateThread(&run_unit_test, &data);

std::cerr << "Setting timer for " << *cmdline_opts.timeout << " ms." << std::endl;
int wait_result = SDL_SemWaitTimeout(worker_sem, *cmdline_opts.timeout);
if (wait_result == 0) {
SDL_SemPost(worker_sem);
SDL_DestroySemaphore(worker_sem);
return worker_result;
} else if (wait_result == -1) {
SDL_KillThread(worker); //don't want worker to keep running when game goes out of scope
std::cerr << "Error in SemWaitTimeout!" << std::endl;
std::cout << ("FAIL TEST (TIMEOUT): ") << *cmdline_opts.unit_test << std::endl;
return 2;
} else {
SDL_KillThread(worker); //don't want worker to keep running when game goes out of scope
std::cerr << "Test timed out!" << std::endl;
std::cout << ("FAIL TEST (TIMEOUT): ") << *cmdline_opts.unit_test << std::endl;
return 2;
}
}
else {
int worker_result = game->unit_test();
std::cout << ((worker_result == 0) ? "PASS TEST: " : "FAIL TEST: ") << *cmdline_opts.unit_test << std::endl;
return worker_result;
}
}

if(game->play_test() == false) {
Expand Down
18 changes: 0 additions & 18 deletions src/game_controller.cpp
Expand Up @@ -249,9 +249,6 @@ game_controller::game_controller(const commandline_options& cmdline_opts, const
{
if (!cmdline_opts_.unit_test->empty()) {
test_scenario_ = *cmdline_opts_.unit_test;
if (cmdline_opts_.timeout) {
timeout = *cmdline_opts_.timeout;
}
}

}
Expand Down Expand Up @@ -444,14 +441,6 @@ bool game_controller::play_test()
return false;
}

unsigned int unit_test_timeout(unsigned int, void* param)
{
std::string * scen = (std::string *)param;
std::cerr << "Test timed out!" << std::endl;
std::cout << ("FAIL TEST (TIMEOUT): ") << *scen << std::endl;
exit(2);
}

// Same as play_test except that we return the results of play_game.
int game_controller::unit_test()
{
Expand All @@ -472,11 +461,6 @@ int game_controller::unit_test()
resources::config_manager->
load_game_config_for_game(state_.classification());

if(cmdline_opts_.timeout && timeout > 0) {
std::cerr << "Adding timer for " << timeout << " ms." << std::endl;
SDL_AddTimer(timeout, unit_test_timeout, & test_scenario_);
}

try {
LEVEL_RESULT res = play_game(disp(),state_,resources::config_manager->game_config());
return ((res == VICTORY || res == NONE) ? 0 : 1);
Expand All @@ -485,8 +469,6 @@ int game_controller::unit_test()
}
}

#undef Uint32

bool game_controller::play_screenshot_mode()
{
if(!cmdline_opts_.screenshot) {
Expand Down
1 change: 0 additions & 1 deletion src/game_controller.hpp
Expand Up @@ -107,7 +107,6 @@ class game_controller
resize_monitor resize_monitor_;

std::string test_scenario_;
unsigned int timeout;

std::string screenshot_map_, screenshot_filename_;

Expand Down

0 comments on commit 58de721

Please sign in to comment.