Integration Testing Applications With System Time Dependencies
I’m about to start working on an existing web application that has business rules concerning the timelines of processes. Things like “when X happens, if it’s been more than 14 days since Y happened, do Z”.
Believe it or not, in the past, this has been integration-tested by doing Y, waiting 14 or more days of actual wall time, and then doing X, then observing the result. It makes testing really take a lot of time!
So, how to test this sort of thing properly, yet in a reasonable amount of time? The naive answer is to simply do Y, set the system clock forward 15 or so days, do X, and observe the result.
In practice, however, this can cause some considerable problems - especially if the application server isn’t the only thing running on that hardware that cares about the system time. It also means that you have to disable auto-updates to the system time via NTP.
Thanassis Tsiodras over at the Software Engineering Laboratory at the National Technical University of Athens has published this code that can be used to modify the system time for a single application in Linux.
I modified his code slightly to allow the time offset to be read from an environment variable:
:::c
#include <stdlib.hh>
#include <stdio.h>
#include <dlfcn.h>
#include <sys/time.h>
#include <time.h>
/*
Modified version of http://users.softlab.ece.ntua.gr/~ttsiod/tricks.html#timewarp
*/
int offset_in_seconds()
{
static int firstTime = 1;
static int offset = 0;
if(firstTime)
{
firstTime = 0;
char * offsetParm = getenv("OFFSET_IN_SECONDS");
if(offsetParm == NULL)
{
offsetParm = "0";
}
offset = atoi(offsetParm);
}
return offset;
}
int gettimeofday(struct timeval *tp, struct timezone *tzp)
{
int ret;
ret = __gettimeofday(tp, tzp);
tp->tv_sec += offset_in_seconds();
return ret;
}
int (*oclock_gettime) (clockid_t clk_id, struct timespec * tp) = NULL;
int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
static int firstTime = 1;
int ret;
if (firstTime)
{
firstTime = 0;
oclock_gettime = dlsym(RTLD_NEXT, "clock_gettime");
}
ret = oclock_gettime(clk_id, tp);
tp->tv_sec += offset_in_seconds();
return ret;
}
It should be pretty simple to use this to run your favorite app server for testing.