Home | History | Annotate | Download | only in base
      1 /*
      2  * libjingle
      3  * Copyright 2004--2011, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #ifdef POSIX
     29 #include <sys/time.h>
     30 #endif  // POSIX
     31 
     32 // TODO: Remove this once the cause of sporadic failures in these
     33 // tests is tracked down.
     34 #include <iostream>
     35 
     36 #ifdef WIN32
     37 #include "talk/base/win32.h"
     38 #endif  // WIN32
     39 
     40 #include "talk/base/common.h"
     41 #include "talk/base/gunit.h"
     42 #include "talk/base/logging.h"
     43 #include "talk/base/task.h"
     44 #include "talk/base/taskrunner.h"
     45 #include "talk/base/thread.h"
     46 #include "talk/base/timeutils.h"
     47 
     48 namespace talk_base {
     49 
     50 static int64 GetCurrentTime() {
     51   return static_cast<int64>(Time()) * 10000;
     52 }
     53 
     54 // feel free to change these numbers.  Note that '0' won't work, though
     55 #define STUCK_TASK_COUNT 5
     56 #define HAPPY_TASK_COUNT 20
     57 
     58 // this is a generic timeout task which, when it signals timeout, will
     59 // include the unique ID of the task in the signal (we don't use this
     60 // in production code because we haven't yet had occasion to generate
     61 // an array of the same types of task)
     62 
     63 class IdTimeoutTask : public Task, public sigslot::has_slots<> {
     64  public:
     65   explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
     66     SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
     67   }
     68 
     69   sigslot::signal1<const int> SignalTimeoutId;
     70   sigslot::signal1<const int> SignalDoneId;
     71 
     72   virtual int ProcessStart() {
     73     return STATE_RESPONSE;
     74   }
     75 
     76   void OnLocalTimeout() {
     77     SignalTimeoutId(unique_id());
     78   }
     79 
     80  protected:
     81   virtual void Stop() {
     82     SignalDoneId(unique_id());
     83     Task::Stop();
     84   }
     85 };
     86 
     87 class StuckTask : public IdTimeoutTask {
     88  public:
     89   explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
     90   virtual int ProcessStart() {
     91     return STATE_BLOCKED;
     92   }
     93 };
     94 
     95 class HappyTask : public IdTimeoutTask {
     96  public:
     97   explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
     98     time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
     99   }
    100   virtual int ProcessStart() {
    101     if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
    102       return STATE_RESPONSE;
    103     else
    104       return STATE_BLOCKED;
    105   }
    106 
    107  private:
    108   int time_to_perform_;
    109 };
    110 
    111 // simple implementation of a task runner which uses Windows'
    112 // GetSystemTimeAsFileTime() to get the current clock ticks
    113 
    114 class MyTaskRunner : public TaskRunner {
    115  public:
    116   virtual void WakeTasks() { RunTasks(); }
    117   virtual int64 CurrentTime() {
    118     return GetCurrentTime();
    119   }
    120 
    121   bool timeout_change() const {
    122     return timeout_change_;
    123   }
    124 
    125   void clear_timeout_change() {
    126     timeout_change_ = false;
    127   }
    128  protected:
    129   virtual void OnTimeoutChange() {
    130     timeout_change_ = true;
    131   }
    132   bool timeout_change_;
    133 };
    134 
    135 //
    136 // this unit test is primarily concerned (for now) with the timeout
    137 // functionality in tasks.  It works as follows:
    138 //
    139 //   * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
    140 //     and some "happy" (will immediately finish).
    141 //   * Set the timeout on the "stuck" tasks to some number of seconds between
    142 //     1 and the number of stuck tasks
    143 //   * Start all the stuck & happy tasks in random order
    144 //   * Wait "number of stuck tasks" seconds and make sure everything timed out
    145 
    146 class TaskTest : public sigslot::has_slots<> {
    147  public:
    148   TaskTest() {}
    149 
    150   // no need to delete any tasks; the task runner owns them
    151   ~TaskTest() {}
    152 
    153   void Start() {
    154     // create and configure tasks
    155     for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
    156       stuck_[i].task_ = new StuckTask(&task_runner_);
    157       stuck_[i].task_->SignalTimeoutId.connect(this,
    158                                                &TaskTest::OnTimeoutStuck);
    159       stuck_[i].timed_out_ = false;
    160       stuck_[i].xlat_ = stuck_[i].task_->unique_id();
    161       stuck_[i].task_->set_timeout_seconds(i + 1);
    162       LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
    163                    << stuck_[i].task_->timeout_seconds();
    164     }
    165 
    166     for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
    167       happy_[i].task_ = new HappyTask(&task_runner_);
    168       happy_[i].task_->SignalTimeoutId.connect(this,
    169                                                &TaskTest::OnTimeoutHappy);
    170       happy_[i].task_->SignalDoneId.connect(this,
    171                                             &TaskTest::OnDoneHappy);
    172       happy_[i].timed_out_ = false;
    173       happy_[i].xlat_ = happy_[i].task_->unique_id();
    174     }
    175 
    176     // start all the tasks in random order
    177     int stuck_index = 0;
    178     int happy_index = 0;
    179     for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
    180       if ((stuck_index < STUCK_TASK_COUNT) &&
    181           (happy_index < HAPPY_TASK_COUNT)) {
    182         if (rand() % 2 == 1) {
    183           stuck_[stuck_index++].task_->Start();
    184         } else {
    185           happy_[happy_index++].task_->Start();
    186         }
    187       } else if (stuck_index < STUCK_TASK_COUNT) {
    188         stuck_[stuck_index++].task_->Start();
    189       } else {
    190         happy_[happy_index++].task_->Start();
    191       }
    192     }
    193 
    194     for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
    195       std::cout << "Stuck task #" << i << " timeout is " <<
    196           stuck_[i].task_->timeout_seconds() << " at " <<
    197           stuck_[i].task_->timeout_time() << std::endl;
    198     }
    199 
    200     // just a little self-check to make sure we started all the tasks
    201     ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
    202     ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
    203 
    204     // run the unblocked tasks
    205     LOG(LS_INFO) << "Running tasks";
    206     task_runner_.RunTasks();
    207 
    208     std::cout << "Start time is " << GetCurrentTime() << std::endl;
    209 
    210     // give all the stuck tasks time to timeout
    211     for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
    212          ++i) {
    213       Thread::Current()->ProcessMessages(1000);
    214       for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
    215         if (happy_[j].task_) {
    216           happy_[j].task_->Wake();
    217         }
    218       }
    219       LOG(LS_INFO) << "Polling tasks";
    220       task_runner_.PollTasks();
    221     }
    222 
    223     // We see occasional test failures here due to the stuck tasks not having
    224     // timed-out yet, which seems like it should be impossible. To help track
    225     // this down we have added logging of the timing information, which we send
    226     // directly to stdout so that we get it in opt builds too.
    227     std::cout << "End time is " << GetCurrentTime() << std::endl;
    228   }
    229 
    230   void OnTimeoutStuck(const int id) {
    231     LOG(LS_INFO) << "Timed out task " << id;
    232 
    233     int i;
    234     for (i = 0; i < STUCK_TASK_COUNT; ++i) {
    235       if (stuck_[i].xlat_ == id) {
    236         stuck_[i].timed_out_ = true;
    237         stuck_[i].task_ = NULL;
    238         break;
    239       }
    240     }
    241 
    242     // getting a bad ID here is a failure, but let's continue
    243     // running to see what else might go wrong
    244     EXPECT_LT(i, STUCK_TASK_COUNT);
    245   }
    246 
    247   void OnTimeoutHappy(const int id) {
    248     int i;
    249     for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
    250       if (happy_[i].xlat_ == id) {
    251         happy_[i].timed_out_ = true;
    252         happy_[i].task_ = NULL;
    253         break;
    254       }
    255     }
    256 
    257     // getting a bad ID here is a failure, but let's continue
    258     // running to see what else might go wrong
    259     EXPECT_LT(i, HAPPY_TASK_COUNT);
    260   }
    261 
    262   void OnDoneHappy(const int id) {
    263     int i;
    264     for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
    265       if (happy_[i].xlat_ == id) {
    266         happy_[i].task_ = NULL;
    267         break;
    268       }
    269     }
    270 
    271     // getting a bad ID here is a failure, but let's continue
    272     // running to see what else might go wrong
    273     EXPECT_LT(i, HAPPY_TASK_COUNT);
    274   }
    275 
    276   void check_passed() {
    277     EXPECT_TRUE(task_runner_.AllChildrenDone());
    278 
    279     // make sure none of our happy tasks timed out
    280     for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
    281       EXPECT_FALSE(happy_[i].timed_out_);
    282     }
    283 
    284     // make sure all of our stuck tasks timed out
    285     for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
    286       EXPECT_TRUE(stuck_[i].timed_out_);
    287       if (!stuck_[i].timed_out_) {
    288         std::cout << "Stuck task #" << i << " timeout is at "
    289             << stuck_[i].task_->timeout_time() << std::endl;
    290       }
    291     }
    292 
    293     std::cout.flush();
    294   }
    295 
    296  private:
    297   struct TaskInfo {
    298     IdTimeoutTask *task_;
    299     bool timed_out_;
    300     int xlat_;
    301   };
    302 
    303   MyTaskRunner task_runner_;
    304   TaskInfo stuck_[STUCK_TASK_COUNT];
    305   TaskInfo happy_[HAPPY_TASK_COUNT];
    306 };
    307 
    308 TEST(start_task_test, Timeout) {
    309   TaskTest task_test;
    310   task_test.Start();
    311   task_test.check_passed();
    312 }
    313 
    314 // Test for aborting the task while it is running
    315 
    316 class AbortTask : public Task {
    317  public:
    318   explicit AbortTask(TaskParent *parent) : Task(parent) {
    319     set_timeout_seconds(1);
    320   }
    321 
    322   virtual int ProcessStart() {
    323     Abort();
    324     return STATE_NEXT;
    325   }
    326  private:
    327   DISALLOW_EVIL_CONSTRUCTORS(AbortTask);
    328 };
    329 
    330 class TaskAbortTest : public sigslot::has_slots<> {
    331  public:
    332   TaskAbortTest() {}
    333 
    334   // no need to delete any tasks; the task runner owns them
    335   ~TaskAbortTest() {}
    336 
    337   void Start() {
    338     Task *abort_task = new AbortTask(&task_runner_);
    339     abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
    340     abort_task->Start();
    341 
    342     // run the task
    343     task_runner_.RunTasks();
    344   }
    345 
    346  private:
    347   void OnTimeout() {
    348     FAIL() << "Task timed out instead of aborting.";
    349   }
    350 
    351   MyTaskRunner task_runner_;
    352   DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest);
    353 };
    354 
    355 TEST(start_task_test, Abort) {
    356   TaskAbortTest abort_test;
    357   abort_test.Start();
    358 }
    359 
    360 // Test for aborting a task to verify that it does the Wake operation
    361 // which gets it deleted.
    362 
    363 class SetBoolOnDeleteTask : public Task {
    364  public:
    365   SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
    366     : Task(parent),
    367       set_when_deleted_(set_when_deleted) {
    368     EXPECT_TRUE(NULL != set_when_deleted);
    369     EXPECT_FALSE(*set_when_deleted);
    370   }
    371 
    372   virtual ~SetBoolOnDeleteTask() {
    373     *set_when_deleted_ = true;
    374   }
    375 
    376   virtual int ProcessStart() {
    377     return STATE_BLOCKED;
    378   }
    379 
    380  private:
    381   bool* set_when_deleted_;
    382   DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask);
    383 };
    384 
    385 class AbortShouldWakeTest : public sigslot::has_slots<> {
    386  public:
    387   AbortShouldWakeTest() {}
    388 
    389   // no need to delete any tasks; the task runner owns them
    390   ~AbortShouldWakeTest() {}
    391 
    392   void Start() {
    393     bool task_deleted = false;
    394     Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
    395     task_to_abort->Start();
    396 
    397     // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
    398     // TaskRunner::RunTasks() immediately which should delete the task.
    399     task_to_abort->Abort();
    400     EXPECT_TRUE(task_deleted);
    401 
    402     if (!task_deleted) {
    403       // avoid a crash (due to referencing a local variable)
    404       // if the test fails.
    405       task_runner_.RunTasks();
    406     }
    407   }
    408 
    409  private:
    410   void OnTimeout() {
    411     FAIL() << "Task timed out instead of aborting.";
    412   }
    413 
    414   MyTaskRunner task_runner_;
    415   DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest);
    416 };
    417 
    418 TEST(start_task_test, AbortShouldWake) {
    419   AbortShouldWakeTest abort_should_wake_test;
    420   abort_should_wake_test.Start();
    421 }
    422 
    423 // Validate that TaskRunner's OnTimeoutChange gets called appropriately
    424 //  * When a task calls UpdateTaskTimeout
    425 //  * When the next timeout task time, times out
    426 class TimeoutChangeTest : public sigslot::has_slots<> {
    427  public:
    428   TimeoutChangeTest()
    429     : task_count_(ARRAY_SIZE(stuck_tasks_)) {}
    430 
    431   // no need to delete any tasks; the task runner owns them
    432   ~TimeoutChangeTest() {}
    433 
    434   void Start() {
    435     for (int i = 0; i < task_count_; ++i) {
    436       stuck_tasks_[i] = new StuckTask(&task_runner_);
    437       stuck_tasks_[i]->set_timeout_seconds(i + 2);
    438       stuck_tasks_[i]->SignalTimeoutId.connect(this,
    439                                                &TimeoutChangeTest::OnTimeoutId);
    440     }
    441 
    442     for (int i = task_count_ - 1; i >= 0; --i) {
    443       stuck_tasks_[i]->Start();
    444     }
    445     task_runner_.clear_timeout_change();
    446 
    447     // At this point, our timeouts are set as follows
    448     // task[0] is 2 seconds, task[1] at 3 seconds, etc.
    449 
    450     stuck_tasks_[0]->set_timeout_seconds(2);
    451     // Now, task[0] is 2 seconds, task[1] at 3 seconds...
    452     // so timeout change shouldn't be called.
    453     EXPECT_FALSE(task_runner_.timeout_change());
    454     task_runner_.clear_timeout_change();
    455 
    456     stuck_tasks_[0]->set_timeout_seconds(1);
    457     // task[0] is 1 seconds, task[1] at 3 seconds...
    458     // The smallest timeout got smaller so timeout change be called.
    459     EXPECT_TRUE(task_runner_.timeout_change());
    460     task_runner_.clear_timeout_change();
    461 
    462     stuck_tasks_[1]->set_timeout_seconds(2);
    463     // task[0] is 1 seconds, task[1] at 2 seconds...
    464     // The smallest timeout is still 1 second so no timeout change.
    465     EXPECT_FALSE(task_runner_.timeout_change());
    466     task_runner_.clear_timeout_change();
    467 
    468     while (task_count_ > 0) {
    469       int previous_count = task_count_;
    470       task_runner_.PollTasks();
    471       if (previous_count != task_count_) {
    472         // We only get here when a task times out.  When that
    473         // happens, the timeout change should get called because
    474         // the smallest timeout is now in the past.
    475         EXPECT_TRUE(task_runner_.timeout_change());
    476         task_runner_.clear_timeout_change();
    477       }
    478       Thread::Current()->socketserver()->Wait(500, false);
    479     }
    480   }
    481 
    482  private:
    483   void OnTimeoutId(const int id) {
    484     for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) {
    485       if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
    486         task_count_--;
    487         stuck_tasks_[i] = NULL;
    488         break;
    489       }
    490     }
    491   }
    492 
    493   MyTaskRunner task_runner_;
    494   StuckTask* (stuck_tasks_[3]);
    495   int task_count_;
    496   DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest);
    497 };
    498 
    499 TEST(start_task_test, TimeoutChange) {
    500   TimeoutChangeTest timeout_change_test;
    501   timeout_change_test.Start();
    502 }
    503 
    504 class DeleteTestTaskRunner : public TaskRunner {
    505  public:
    506   DeleteTestTaskRunner() {
    507   }
    508   virtual void WakeTasks() { }
    509   virtual int64 CurrentTime() {
    510     return GetCurrentTime();
    511   }
    512  private:
    513   DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner);
    514 };
    515 
    516 TEST(unstarted_task_test, DeleteTask) {
    517   // This test ensures that we don't
    518   // crash if a task is deleted without running it.
    519   DeleteTestTaskRunner task_runner;
    520   HappyTask* happy_task = new HappyTask(&task_runner);
    521   happy_task->Start();
    522 
    523   // try deleting the task directly
    524   HappyTask* child_happy_task = new HappyTask(happy_task);
    525   delete child_happy_task;
    526 
    527   // run the unblocked tasks
    528   task_runner.RunTasks();
    529 }
    530 
    531 TEST(unstarted_task_test, DoNotDeleteTask1) {
    532   // This test ensures that we don't
    533   // crash if a task runner is deleted without
    534   // running a certain task.
    535   DeleteTestTaskRunner task_runner;
    536   HappyTask* happy_task = new HappyTask(&task_runner);
    537   happy_task->Start();
    538 
    539   HappyTask* child_happy_task = new HappyTask(happy_task);
    540   child_happy_task->Start();
    541 
    542   // Never run the tasks
    543 }
    544 
    545 TEST(unstarted_task_test, DoNotDeleteTask2) {
    546   // This test ensures that we don't
    547   // crash if a taskrunner is delete with a
    548   // task that has never been started.
    549   DeleteTestTaskRunner task_runner;
    550   HappyTask* happy_task = new HappyTask(&task_runner);
    551   happy_task->Start();
    552 
    553   // Do not start the task.
    554   // Note: this leaks memory, so don't do this.
    555   // Instead, always run your tasks or delete them.
    556   new HappyTask(happy_task);
    557 
    558   // run the unblocked tasks
    559   task_runner.RunTasks();
    560 }
    561 
    562 }  // namespace talk_base
    563