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