Every now and then I need to acceptance-test some date-related code. Consider a following scenario to test a default issue date of an invoice:
Given today is 15 July 2009 When I issue an invoice with no date specified Then issue date is 15 July 2009
How to implement the Given today is 15 July 2009 bit?
What I have seen so far in Java world was a specialised ,,GiveMeDate'' class, with two implementations: one real (being a wrapper around the standard time classes), and one just for the test time (returning a date specified in a test). An appropriate implementation would be injected an IOC container. While one may argue that in object-oriented programming it is a good practice anyway, to introduce a freedom of choice of an implementation, in Haskell it doesn't feel very right (at least for my taste). Is there an alternative?
Today I decided to try out a technique called library interposing. It allows me to intercept an FFI call and return whatever date I want.
I wrote a little library gtod_interposer.c that defines gettimeofday(2) and made any application use it with the dynamic loader. Here is how exactly:
$ cat gtod_interposer.c
#include <dlfcn.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
bool is_set(char *str)
{
return (str != NULL) && (str[0] != 0);
}
int parse_date(char *date, struct timeval *tp) {
struct tm tm;
if (strptime(date, "%Y-%m-%d %H:%M:%S", &tm) != NULL)
{
(*tp).tv_sec = mktime(&tm);
(*tp).tv_usec = 0;
return 0;
}
else
{
return EFAULT; // well, not exactly it
// but at least this is an expected value
}
}
int gettimeofday(struct timeval *tp, void *tzp)
{
char *env_test_date = getenv("TEST_DATE");
if (is_set(env_test_date))
return parse_date(env_test_date, tp);
else
return ((int (*)()) dlsym(RTLD_NEXT, "gettimeofday"))(tp,tzp);
}
$ cc -o gtod_interposer.dylib -G -Kpic -dynamiclib gtod_interposer.c
$ export DYLD_INSERT_LIBRARIES="/Users/irek/Projects/firmus/gtod_interposer.dylib"
$ ghc -e "System.Time.getClockTime" # actual date
Sun Jan 10 23:16:40 CET 2010
$ export TEST_DATE="1982-07-15 12:00:00"
$ ghc -e "System.Time.getClockTime" # now the date is set by me
Thu Jul 15 12:00:00 CEST 1982
$ export TEST_DATE="" # unset TEST_DATE would do as well
$ ghc -e "System.Time.getClockTime" # back to the actual date
Sun Jan 10 23:17:23 CET 2010
Note: this works for Mac OS X. For Linux it would be very similar, check the link below.
The code has been very much inspired by Debugging and Performance Tuning with Library Interposers.