If possible, how do you fake the time for the purpose of triggering boost
timers in a unit test?
A minimal example of achieving the requirements, based on a blog post by Chris
(author of boost.asio)
Thanks to the SO community for the providing hints.
#include
#include
class mock_time_traits
{
typedef boost::asio::deadline_timer::traits_type source_traits;
public:
typedef source_traits::time_type time_type;
typedef source_traits::duration_type duration_type;
// Note this implemenation requires set_now(...) to be called before now()
static time_type now() { return *now_; }
// After modifying the clock, we need to sleep the thread to give the
io_service
// the opportunity to poll and notice the change in clock time
static void set_now(time_type t)
{
now_ = t;
boost::this_thread::sleep_for(boost::chrono::milliseconds(2));
}
static time_type add(time_type t, duration_type d) { return
source_traits::add(t, d); }
static duration_type subtract(time_type t1, time_type t2) { return
source_traits::subtract(t1, t2); }
static bool less_than(time_type t1, time_type t2) { return
source_traits::less_than(t1, t2); }
// This function is called by asio to determine how often to check
// if the timer is ready to fire. By manipulating this function, we
// can make sure asio detects changes to now_ in a timely fashion.
static boost::posix_time::time_duration to_posix_duration(duration_type d)
{
return d < boost::posix_time::milliseconds(1) ? d :
boost::posix_time::milliseconds(1);
}
private:
static boost::optional now_;
};
boost::optional mock_time_traits::now_;
typedef boost::asio::basic_deadline_timer<
boost::posix_time::ptime, mock_time_traits> mock_deadline_timer;
void handler(const boost::system::error_code &ec)
{
std::cout << "Handler!" << std::endl;
}
int main()
{
mock_time_traits::set_now(boost::posix_time::time_from_string("2013-01-20
1:44:01.000"));
boost::asio::io_service io_service;
mock_deadline_timer timer(io_service, boost::posix_time::seconds(5));
timer.async_wait(handler);
std::cout << "Poll 1" << std::endl;
io_service.poll();
mock_time_traits::set_now(mock_time_traits::now() +
boost::posix_time::seconds(6));
std::cout << "Poll 2" << std::endl;
io_service.poll();
std::cout << "Poll 3" << std::endl;
io_service.poll();
return 0;
}
// Output
Poll 1
Poll 2
Handler!
Poll 3
The above is slightly modified (and I believe slightly improved) on the blog
post.
By not using an offset on the system clock, you gain complete control over the
timers:
they will not fire until you explicitly set time forward past the timer.
The solution could be improved by making the this_thread::sleep part
configurable.
Note that the to_posix_duration hack described in [1] needs to use a smaller
duration than the sleep.
To me this approach still seems a bit magic, since the time_traits are not well
documented,
and in particular the hack of to_posix_duration has a whiff of voodoo about it.
I guess it just comes down to intimate knowledge of the deadline_timer
implementation (which I don't have).