Boost logo

Boost Users :

Subject: Re: [Boost-users] [asio] How do you fake the time for boost timers?
From: Josh (0zeroth_at_[hidden])
Date: 2013-01-19 23:24:50


> 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 <boost/asio.hpp>
#include <boost/optional.hpp>

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<time_type> now_;
};

boost::optional<mock_time_traits::time_type> 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).


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net