interval_timer_unittest.cc 11.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <config.h>
#include <gtest/gtest.h>

#include <asiolink/asiolink.h>

#include <boost/date_time/posix_time/posix_time_types.hpp>

namespace {
// TODO: Consider this margin
const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
    boost::posix_time::milliseconds(50);
}

using namespace asiolink;

// This fixture is for testing IntervalTimer. Some callback functors are 
// registered as callback function of the timer to test if they are called
// or not.
class IntervalTimerTest : public ::testing::Test {
protected:
    IntervalTimerTest() : io_service_() {}
    ~IntervalTimerTest() {}
    class TimerCallBack : public std::unary_function<void, void> {
    public:
        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
        void operator()() const {
            test_obj_->timer_called_ = true;
            test_obj_->io_service_.stop();
            return;
        }
    private:
        IntervalTimerTest* test_obj_;
    };
    class TimerCallBackCounter : public std::unary_function<void, void> {
    public:
        TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
            counter_ = 0;
        }
        void operator()() {
            ++counter_;
            return;
        }
        int counter_;
    private:
        IntervalTimerTest* test_obj_;
    };
    class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
    public:
        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
                                   IntervalTimer* timer,
                                   TimerCallBackCounter& counter)
            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
        {}
        void operator()() {
            ++count_;
            if (count_ == 1) {
                // First time of call back.
                // Store the value of counter_.counter_.
                prev_counter_ = counter_.counter_;
                delete timer_;
            } else if (count_ == 2) {
                // Second time of call back.
                // Stop io_service to stop all timers.
                test_obj_->io_service_.stop();
                // Compare the value of counter_.counter_ with stored one.
                // If TimerCallBackCounter was not called (expected behavior),
                // they are same.
                if (counter_.counter_ == prev_counter_) {
                    test_obj_->timer_cancel_success_ = true;
                }
            }
            return;
        }
    private:
        IntervalTimerTest* test_obj_;
        IntervalTimer* timer_;
        TimerCallBackCounter& counter_;
        int count_;
        int prev_counter_;
    };
    class TimerCallBackCanceller {
    public:
        TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
            counter_(counter), itimer_(itimer)
        {}
        void operator()() {
            ++counter_;
            itimer_.cancel();
        }
    private:
        unsigned int& counter_;
        IntervalTimer& itimer_;
    };
    class TimerCallBackOverwriter : public std::unary_function<void, void> {
    public:
        TimerCallBackOverwriter(IntervalTimerTest* test_obj,
                                IntervalTimer& timer)
            : test_obj_(test_obj), timer_(timer), count_(0)
        {}
        void operator()() {
            ++count_;
            if (count_ == 1) {
                // First time of call back.
                // Call setup() to update callback function to TimerCallBack.
                test_obj_->timer_called_ = false;
                timer_.setup(TimerCallBack(test_obj_), 100);
            } else if (count_ == 2) {
                // Second time of call back.
                // If it reaches here, re-setup() is failed (unexpected).
                // We should stop here.
                test_obj_->io_service_.stop();
            }
            return;
        }
    private:
        IntervalTimerTest* test_obj_;
        IntervalTimer& timer_;
        int count_;
    };
protected:
    IOService io_service_;
    bool timer_called_;
    bool timer_cancel_success_;
};

TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
    // Create asio_link::IntervalTimer and setup.
    IntervalTimer itimer(io_service_);
    // expect throw if call back function is empty
    EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
                 isc::InvalidParameter);
    // expect throw if interval is not greater than 0
    EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
    EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
}

TEST_F(IntervalTimerTest, startIntervalTimer) {
    // Create asio_link::IntervalTimer and setup.
    // Then run IOService and test if the callback function is called.
    IntervalTimer itimer(io_service_);
    timer_called_ = false;
    // store start time
    boost::posix_time::ptime start;
    start = boost::posix_time::microsec_clock::universal_time();
    // setup timer
    itimer.setup(TimerCallBack(this), 100);
    EXPECT_EQ(100, itimer.getInterval());
    io_service_.run();
    // reaches here after timer expired
    // delta: difference between elapsed time and 100 milliseconds.
    boost::posix_time::time_duration delta =
        (boost::posix_time::microsec_clock::universal_time() - start)
         - boost::posix_time::millisec(100);
    if (delta.is_negative()) {
        delta.invert_sign();
    }
    // expect TimerCallBack is called; timer_called_ is true
    EXPECT_TRUE(timer_called_);
    // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
}

TEST_F(IntervalTimerTest, destructIntervalTimer) {
    // This code isn't exception safe, but we'd rather keep the code
    // simpler and more readable as this is only for tests and if it throws
    // the program would immediately terminate anyway.

    // The call back function will not be called after the timer is
    // destroyed.
    //
    // There are two timers:
    //  itimer_counter (A)
    //   (Calls TimerCallBackCounter)
    //     - increments internal counter in callback function
    //  itimer_canceller (B)
    //   (Calls TimerCallBackCancelDeleter)
    //     - first time of callback, it stores the counter value of
    //       callback_canceller and destroys itimer_counter
    //     - second time of callback, it compares the counter value of
    //       callback_canceller with stored value
    //       if they are same the timer was not called; expected result
    //       if they are different the timer was called after destroyed
    //
    //     0  100  200  300  400  500  600 (ms)
    // (A) i--------+----x
    //                   ^
    //                   |destroy itimer_counter
    // (B) i-------------+--------------s
    //                                  ^stop io_service
    //                                   and check if itimer_counter have been
    //                                   stopped

    // itimer_counter will be deleted in TimerCallBackCancelDeleter
    IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
    IntervalTimer itimer_canceller(io_service_);
    timer_cancel_success_ = false;
    TimerCallBackCounter callback_canceller(this);
    itimer_counter->setup(callback_canceller, 200);
    itimer_canceller.setup(
        TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
        300);
    io_service_.run();
    EXPECT_TRUE(timer_cancel_success_);
}

TEST_F(IntervalTimerTest, cancel) {
    // Similar to destructIntervalTimer test, but the first timer explicitly
    // cancels itself on first callback.
    IntervalTimer itimer_counter(io_service_);
    IntervalTimer itimer_watcher(io_service_);
    unsigned int counter = 0;
    itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
    itimer_watcher.setup(TimerCallBack(this), 200);
    io_service_.run();
    EXPECT_EQ(1, counter);
    EXPECT_EQ(0, itimer_counter.getInterval());

    // canceling an already canceled timer shouldn't cause any surprise.
    EXPECT_NO_THROW(itimer_counter.cancel());
}

TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
    // Calling setup() multiple times updates call back function and interval.
    //
    // There are two timers:
    //  itimer (A)
    //   (Calls TimerCallBackCounter / TimerCallBack)
    //     - increments internal counter in callback function
    //       (TimerCallBackCounter)
    //       interval: 300 milliseconds
    //     - io_service_.stop() (TimerCallBack)
    //       interval: 100 milliseconds
    //  itimer_overwriter (B)
    //   (Calls TimerCallBackOverwriter)
    //     - first time of callback, it calls setup() to change call back
    //       function to TimerCallBack and interval of itimer to 100
    //       milliseconds
    //       after 300 + 100 milliseconds from the beginning of this test,
    //       TimerCallBack() will be called and io_service_ stops.
    //     - second time of callback, it means the test fails.
    //
    //     0  100  200  300  400  500  600  700  800 (ms)
    // (A) i-------------+----C----s
    //                        ^    ^stop io_service
    //                        |change call back function
    // (B) i------------------+-------------------S
    //                                            ^(stop io_service on fail)
    //

    IntervalTimer itimer(io_service_);
    IntervalTimer itimer_overwriter(io_service_);
    // store start time
    boost::posix_time::ptime start;
    start = boost::posix_time::microsec_clock::universal_time();
    itimer.setup(TimerCallBackCounter(this), 300);
    itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
    io_service_.run();
    // reaches here after timer expired
    // if interval is updated, it takes
    //   400 milliseconds for TimerCallBackOverwriter
    //   + 100 milliseconds for TimerCallBack (stop)
    //   = 500 milliseconds.
    // otherwise (test fails), it takes
    //   400 milliseconds for TimerCallBackOverwriter
    //   + 400 milliseconds for TimerCallBackOverwriter (stop)
    //   = 800 milliseconds.
    // delta: difference between elapsed time and 400 + 100 milliseconds
    boost::posix_time::time_duration delta =
        (boost::posix_time::microsec_clock::universal_time() - start)
         - boost::posix_time::millisec(400 + 100);
    if (delta.is_negative()) {
        delta.invert_sign();
    }
    // expect callback function is updated: TimerCallBack is called
    EXPECT_TRUE(timer_called_);
    // expect interval is updated
    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
}