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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/asiolink.h>

#include <boost/date_time/posix_time/posix_time.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <gtest/gtest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::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_(new IOService()), timer_called_(false),
        timer_cancel_success_(false) {
    }
    ~IntervalTimerTest() {
        io_service_->stopAndPoll();
    }
    class TimerCallBack {
    public:
        TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
        void operator()() const {
            test_obj_->timer_called_ = true;
            test_obj_->io_service_->stop();
        }
    private:
        IntervalTimerTest* test_obj_;
    };
    class TimerCallBackCounter {
    public:
        TimerCallBackCounter(IntervalTimerTest* /* test_obj */) {
            counter_ = 0;
        }
        void operator()() {
            ++counter_;
        }
        int counter_;
    };
    class TimerCallBackCancelDeleter {
    public:
        TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
                                   IntervalTimer* timer,
                                   TimerCallBackCounter& counter)
            : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0),
              prev_counter_(-1) {
        }
        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;
                }
            }
        }
    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:
        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();
            }
        }
    private:
        IntervalTimerTest* test_obj_;
        IntervalTimer& timer_;
        int count_;
    };
    class TimerCallBackAccumulator {
    public:
        TimerCallBackAccumulator(IntervalTimerTest* /* test_obj */, int &counter) :
            counter_(counter) {
        }
        void operator()() {
            ++counter_;
        }
    private:
        // Reference to integer accumulator
        int& counter_;
    };
protected:
    IOServicePtr 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 negative.
    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();
    // Control reaches here after io_service_ was stopped by TimerCallBack.

    // delta: difference between elapsed time and 100 milliseconds.
    boost::posix_time::time_duration test_runtime =
        boost::posix_time::microsec_clock::universal_time() - start;
    EXPECT_FALSE(test_runtime.is_negative()) <<
                 "test duration " << test_runtime <<
                 " negative - clock skew?";
    // Expect TimerCallBack is called; timer_called_ is true
    EXPECT_TRUE(timer_called_);
    // Expect test_runtime is 100 milliseconds or longer.
    // Allow 1% of clock skew
    EXPECT_TRUE(test_runtime >= boost::posix_time::milliseconds(99)) <<
                "test runtime " << test_runtime.total_milliseconds() <<
                "msec >= 100";
}

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) {
    // Call setup() multiple times to update 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 and interval
    // (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();
    // Control reaches here after io_service_ was stopped by
    // TimerCallBackCounter or TimerCallBackOverwriter.

    // Expect callback function is updated: TimerCallBack is called
    EXPECT_TRUE(timer_called_);
    // Expect interval is updated: return value of getInterval() is updated
    EXPECT_EQ(itimer.getInterval(), 100);
}

// This test verifies that timers operate correctly based on their mode.
TEST_F(IntervalTimerTest, intervalModeTest) {
    // Create a timer to control the duration of the test.
    IntervalTimer test_timer(io_service_);
    test_timer.setup(TimerCallBack(this), 2000);

    // Create an timer which automatically reschedules itself.  Use the
    // accumulator callback to increment local counter for it.
    int repeater_count = 0;
    IntervalTimer repeater(io_service_);
    repeater.setup(TimerCallBackAccumulator(this, repeater_count), 10);

    // Create a one-shot timer. Use the accumulator callback to increment
    // local counter variable for it.
    int one_shot_count = 0;
    IntervalTimer one_shot(io_service_);
    one_shot.setup(TimerCallBackAccumulator(this, one_shot_count), 10,
                   IntervalTimer::ONE_SHOT);

    // As long as service runs at least one event handler, loop until
    // we've hit our goals.  It won't return zero unless is out of
    // work or the service has been stopped by the test timer.
    int cnt = 0;
    while (((cnt = io_service_->runOne()) > 0) && (repeater_count < 5)) {
        // deliberately empty
    };

    // If cnt is zero, then something went wrong.
    EXPECT_TRUE(cnt > 0);

    // The loop stopped make sure it was for the right reason.
    EXPECT_EQ(repeater_count, 5);
    EXPECT_EQ(one_shot_count, 1);
}

// This test verifies that the same timer can be reused in either mode.
TEST_F(IntervalTimerTest, timerReuseTest) {
    // Create a timer to control the duration of the test.
    IntervalTimer test_timer(io_service_);
    test_timer.setup(TimerCallBack(this), 2000);

    // Create a one-shot timer. Use the accumulator callback to increment
    // local counter variable for it.
    int one_shot_count = 0;
    IntervalTimer one_shot(io_service_);
    TimerCallBackAccumulator callback(this, one_shot_count);
    one_shot.setup(callback, 10, IntervalTimer::ONE_SHOT);

    // Run until a single event handler executes.  This should be our
    // one-shot expiring.
    io_service_->runOne();

    // Verify the timer expired once.
    ASSERT_EQ(one_shot_count, 1);

    // Setup the one-shot to go again.
    one_shot.setup(callback, 10, IntervalTimer::ONE_SHOT);

    // Run until a single event handler executes.  This should be our
    // one-shot expiring.
    io_service_->runOne();

    // Verify the timer expired once.
    ASSERT_EQ(one_shot_count, 2);

    // Setup the timer to be repeating.
    one_shot.setup(callback, 10, IntervalTimer::REPEATING);

    // As long as service runs at least one event handler, loop until
    // we've hit our goals.  It won't return zero unless is out of
    // work or the service has been stopped by the test timer.
    int cnt = 0;
    while ((cnt = io_service_->runOne()) && (one_shot_count < 4)) {
        // deliberately empty
    };

    // If cnt is zero, then something went wrong.
    EXPECT_TRUE(cnt > 0);

    // Verify the timer repeated.
    EXPECT_GE(one_shot_count, 4);
}