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